remove src/lib from this repository, prepare for submodule
The contents of src/lib will be replaced with a submodule in a following commmit
This commit is contained in:
parent
9ed1f48b5d
commit
5c474118b1
|
@ -1,7 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libaaimage.a
|
||||
|
||||
libaaimage_a_SOURCES = \
|
||||
aaimage.c
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "aaimage.h"
|
||||
|
||||
extern const char *aaimage[];
|
||||
|
||||
void print_aaimage(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; aaimage[i]; i++) {
|
||||
for (j = 0; j < (int)strlen(aaimage[i]); j++) {
|
||||
if (aaimage[i][j] == '@') {
|
||||
j++;
|
||||
switch(aaimage[i][j]) {
|
||||
case 'k': /* black */
|
||||
printf("\033[0;30m");
|
||||
break;
|
||||
case 'r': /* red */
|
||||
printf("\033[0;31m");
|
||||
break;
|
||||
case 'g': /* green */
|
||||
printf("\033[0;32m");
|
||||
break;
|
||||
case 'y': /* yellow */
|
||||
printf("\033[0;33m");
|
||||
break;
|
||||
case 'b': /* blue */
|
||||
printf("\033[0;34m");
|
||||
break;
|
||||
case 'm': /* magenta */
|
||||
printf("\033[0;35m");
|
||||
break;
|
||||
case 'c': /* cyan */
|
||||
printf("\033[0;36m");
|
||||
break;
|
||||
case 'w': /* white */
|
||||
printf("\033[0;37m");
|
||||
break;
|
||||
case 'K': /* bright black */
|
||||
printf("This will not work on some terminals, please use 'w'\n");
|
||||
abort();
|
||||
case 'R': /* bright red */
|
||||
printf("\033[1;31m");
|
||||
break;
|
||||
case 'G': /* bright green */
|
||||
printf("\033[1;32m");
|
||||
break;
|
||||
case 'Y': /* bright yellow */
|
||||
printf("\033[1;33m");
|
||||
break;
|
||||
case 'B': /* bright blue */
|
||||
printf("\033[1;34m");
|
||||
break;
|
||||
case 'M': /* bright magenta */
|
||||
printf("\033[1;35m");
|
||||
break;
|
||||
case 'C': /* bright cyan */
|
||||
printf("\033[1;36m");
|
||||
break;
|
||||
case 'W': /* bright white */
|
||||
printf("\033[1;37m");
|
||||
break;
|
||||
}
|
||||
} else
|
||||
printf("%c", aaimage[i][j]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
printf("\033[0;39m");
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
|
||||
void print_aaimage(void);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libam.a
|
||||
|
||||
libam_a_SOURCES = \
|
||||
am.c
|
|
@ -1,204 +0,0 @@
|
|||
/* AM modulation and de-modulation
|
||||
*
|
||||
* (C) 2018 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 "../libsample/sample.h"
|
||||
#include "am.h"
|
||||
|
||||
static int has_init = 0;
|
||||
static int fast_math = 0;
|
||||
static float *sin_tab = NULL, *cos_tab = NULL;
|
||||
|
||||
/* global init */
|
||||
int am_init(int _fast_math)
|
||||
{
|
||||
fast_math = _fast_math;
|
||||
|
||||
if (fast_math) {
|
||||
int i;
|
||||
|
||||
sin_tab = calloc(65536+16384, sizeof(*sin_tab));
|
||||
if (!sin_tab) {
|
||||
fprintf(stderr, "No mem!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
cos_tab = sin_tab + 16384;
|
||||
|
||||
/* generate sine and cosine */
|
||||
for (i = 0; i < 65536+16384; i++)
|
||||
sin_tab[i] = sin(2.0 * M_PI * (double)i / 65536.0);
|
||||
}
|
||||
|
||||
has_init = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* global exit */
|
||||
void am_exit(void)
|
||||
{
|
||||
if (sin_tab) {
|
||||
free(sin_tab);
|
||||
sin_tab = cos_tab = NULL;
|
||||
}
|
||||
|
||||
has_init = 0;
|
||||
}
|
||||
|
||||
#define CARRIER_FILTER 30.0
|
||||
|
||||
/* Amplitude modulation in SDR:
|
||||
* Just use the base band (audio signal) as real value, and 0.0 as imaginary
|
||||
* value. The you have two side bands. Be sure to have a DC level, so you
|
||||
* have a carrier.
|
||||
*/
|
||||
|
||||
int am_mod_init(am_mod_t *mod, double samplerate, double offset, double gain, double bias)
|
||||
{
|
||||
memset(mod, 0, sizeof(*mod));
|
||||
mod->gain = gain;
|
||||
mod->bias = bias;
|
||||
if (fast_math)
|
||||
mod->rot = 65536.0 * offset / samplerate;
|
||||
else
|
||||
mod->rot = 2.0 * M_PI * offset / samplerate;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void am_mod_exit(am_mod_t __attribute__((unused)) *mod)
|
||||
{
|
||||
}
|
||||
|
||||
void am_modulate_complex(am_mod_t *mod, sample_t *amplitude, uint8_t *power, int num, float *baseband)
|
||||
{
|
||||
int s;
|
||||
double vector;
|
||||
double rot = mod->rot;
|
||||
double phase = mod->phase;
|
||||
double gain = mod->gain;
|
||||
double bias = mod->bias;
|
||||
|
||||
for (s = 0; s < num; s++) {
|
||||
if (*power++)
|
||||
vector = *amplitude++ * gain + bias;
|
||||
else
|
||||
vector = 0.0;
|
||||
if (fast_math) {
|
||||
*baseband++ += cos_tab[(uint16_t)phase] * vector;
|
||||
*baseband++ += sin_tab[(uint16_t)phase] * vector;
|
||||
phase += rot;
|
||||
if (phase < 0.0)
|
||||
phase += 65536.0;
|
||||
else if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
} else {
|
||||
*baseband++ += cos(phase) * vector;
|
||||
*baseband++ += sin(phase) * vector;
|
||||
phase += rot;
|
||||
if (phase < 0.0)
|
||||
phase += 2.0 * M_PI;
|
||||
else if (phase >= 2.0 * M_PI)
|
||||
phase -= 2.0 * M_PI;
|
||||
}
|
||||
}
|
||||
|
||||
mod->phase = phase;
|
||||
}
|
||||
|
||||
/* init AM demodulator */
|
||||
int am_demod_init(am_demod_t *demod, double samplerate, double offset, double bandwidth, double gain)
|
||||
{
|
||||
memset(demod, 0, sizeof(*demod));
|
||||
demod->gain = gain;
|
||||
if (fast_math)
|
||||
demod->rot = 65536.0 * -offset / samplerate;
|
||||
else
|
||||
demod->rot = 2 * M_PI * -offset / samplerate;
|
||||
|
||||
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
|
||||
iir_lowpass_init(&demod->lp[0], bandwidth, samplerate, 2);
|
||||
iir_lowpass_init(&demod->lp[1], bandwidth, samplerate, 2);
|
||||
|
||||
/* filter carrier */
|
||||
iir_lowpass_init(&demod->lp[2], CARRIER_FILTER, samplerate, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void am_demod_exit(am_demod_t __attribute__((unused)) *demod)
|
||||
{
|
||||
}
|
||||
|
||||
/* do amplitude demodulation of baseband and write them to samples */
|
||||
void am_demodulate_complex(am_demod_t *demod, sample_t *amplitude, int length, float *baseband, sample_t *I, sample_t *Q, sample_t *carrier)
|
||||
{
|
||||
int s, ss;
|
||||
double rot = demod->rot;
|
||||
double phase = demod->phase;
|
||||
double gain = demod->gain;
|
||||
double i, q;
|
||||
double _sin, _cos;
|
||||
|
||||
/* rotate spectrum */
|
||||
for (s = 0, ss = 0; s < length; s++) {
|
||||
i = baseband[ss++];
|
||||
q = baseband[ss++];
|
||||
phase += rot;
|
||||
if (fast_math) {
|
||||
if (phase < 0.0)
|
||||
phase += 65536.0;
|
||||
else if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
_sin = sin_tab[(uint16_t)phase];
|
||||
_cos = cos_tab[(uint16_t)phase];
|
||||
} else {
|
||||
if (phase < 0.0)
|
||||
phase += 2.0 * M_PI;
|
||||
else if (phase >= 2.0 * M_PI)
|
||||
phase -= 2.0 * M_PI;
|
||||
_sin = sin(phase);
|
||||
_cos = cos(phase);
|
||||
}
|
||||
I[s] = i * _cos - q * _sin;
|
||||
Q[s] = i * _sin + q * _cos;
|
||||
}
|
||||
demod->phase = phase;
|
||||
|
||||
/* filter bandwidth */
|
||||
iir_process(&demod->lp[0], I, length);
|
||||
iir_process(&demod->lp[1], Q, length);
|
||||
|
||||
/* demod */
|
||||
for (s = 0; s < length; s++)
|
||||
amplitude[s] = carrier[s] = sqrt(I[s] * I[s] + Q[s] * Q[s]);
|
||||
|
||||
/* filter carrier */
|
||||
iir_process(&demod->lp[2], carrier, length);
|
||||
|
||||
/* normalize */
|
||||
for (s = 0; s < length; s++)
|
||||
amplitude[s] = (amplitude[s] - carrier[s]) / carrier[s] * gain;
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#include "../libfilter/iir_filter.h"
|
||||
|
||||
int am_init(int fast_math);
|
||||
void am_exit(void);
|
||||
|
||||
typedef struct am_mod {
|
||||
double rot; /* angle to rotate vector per sample */
|
||||
double phase; /* current phase */
|
||||
double gain; /* gain to be multiplied to amplitude */
|
||||
double bias; /* DC offset to add (carrier amplitude) */
|
||||
} am_mod_t;
|
||||
|
||||
int am_mod_init(am_mod_t *mod, double samplerate, double offset, double gain, double bias);
|
||||
void am_mod_exit(am_mod_t *mod);
|
||||
void am_modulate_complex(am_mod_t *mod, sample_t *amplitude, uint8_t *power, int num, float *baseband);
|
||||
|
||||
typedef struct am_demod {
|
||||
double rot; /* angle to rotate vector per sample */
|
||||
double phase; /* current rotation phase (used to shift) */
|
||||
iir_filter_t lp[3]; /* filters received IQ signal/carrier */
|
||||
double gain; /* gain to be expected from amplitude */
|
||||
double bias; /* DC offset to be expected (carrier amplitude) */
|
||||
} am_demod_t;
|
||||
|
||||
int am_demod_init(am_demod_t *demod, double samplerate, double offset, double gain, double bias);
|
||||
void am_demod_exit(am_demod_t *demod);
|
||||
void am_demodulate_complex(am_demod_t *demod, sample_t *amplitude, int length, float *baseband, sample_t *I, sample_t *Q, sample_t *carrier);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libclipper.a
|
||||
|
||||
libclipper_a_SOURCES = \
|
||||
clipper.c
|
|
@ -1,81 +0,0 @@
|
|||
/* Clipper implementation, based on code by Jonathan Olds
|
||||
*
|
||||
* (C) 2017 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 <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "clipper.h"
|
||||
|
||||
static double clipper_lut[6000];
|
||||
|
||||
static double clipper_point = NAN;
|
||||
|
||||
void clipper_init(double point)
|
||||
{
|
||||
double a;
|
||||
int i;
|
||||
|
||||
if (point > 0.99)
|
||||
point = 0.99;
|
||||
if (point < 0.01)
|
||||
point = 0.01;
|
||||
clipper_point = point;
|
||||
|
||||
a = M_PI / (2.0 * (1.0 - clipper_point));
|
||||
|
||||
for (i = 0; i < 6000; i++)
|
||||
clipper_lut[i] = clipper_point + atan(a * i / 1000.0) / a;
|
||||
}
|
||||
|
||||
void clipper_process(sample_t *samples, int length)
|
||||
{
|
||||
int i;
|
||||
double val, inv, shiftmultval;
|
||||
int n, q;
|
||||
|
||||
if (isnan(clipper_point)) {
|
||||
fprintf(stderr, "Clipper not initialized, aborting!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
val = samples[i];
|
||||
if (val < 0) {
|
||||
inv = -1.0;
|
||||
val = -val;
|
||||
} else
|
||||
inv = 1.0;
|
||||
shiftmultval = (val - clipper_point) * 1000.0;
|
||||
/* no clipping up to clipping point */
|
||||
if (shiftmultval <= 0.0)
|
||||
continue;
|
||||
n = (int)shiftmultval;
|
||||
q = n + 1;
|
||||
if (q >= 6000) {
|
||||
samples[i] = inv;
|
||||
continue;
|
||||
}
|
||||
/* get clipped value from lut, interpolate between table entries */
|
||||
val = clipper_lut[n] + (shiftmultval - (double)n) * (clipper_lut[q] - clipper_lut[n]);
|
||||
samples[i] = val * inv;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
void clipper_init(double point);
|
||||
void clipper_process(sample_t *samples, int length);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libcompandor.a
|
||||
|
||||
libcompandor_a_SOURCES = \
|
||||
compandor.c
|
|
@ -1,141 +0,0 @@
|
|||
/* Compandor to use various networks like C-Netz / NMT / AMPS / TACS
|
||||
*
|
||||
* (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 <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "compandor.h"
|
||||
|
||||
//#define db2level(db) pow(10, (double)db / 20.0)
|
||||
|
||||
/* factor is the gain (raise and fall) after given attack/recovery time */
|
||||
#define COMPRESS_ATTACK_FACTOR 1.83 /* about 1.5 after 12 dB step up */
|
||||
#define COMPRESS_RECOVERY_FACTOR 0.44 /* about 0.75 after 12 dB step down */
|
||||
#define EXPAND_ATTACK_FACTOR 1.145 /* about 0.57 after 6 dB step up */
|
||||
#define EXPAND_RECOVERY_FACTOR 0.753 /* about 1.51 after 6 dB step down */
|
||||
|
||||
/* Minimum level value to keep state (-60 dB) */
|
||||
#define ENVELOPE_MIN 0.001
|
||||
|
||||
/* Maximum level, to prevent sqrt_tab to overflow */
|
||||
#define ENVELOPE_MAX 9.990
|
||||
|
||||
static double sqrt_tab[10000];
|
||||
|
||||
/*
|
||||
* Init compandor according to ITU-T G.162 specification
|
||||
*
|
||||
* Hopefully this is correct
|
||||
*
|
||||
*/
|
||||
void init_compandor(compandor_t *state, double samplerate, double attack_ms, double recovery_ms)
|
||||
{
|
||||
int i;
|
||||
|
||||
memset(state, 0, sizeof(*state));
|
||||
|
||||
state->c.peak = 1.0;
|
||||
state->c.envelope = 1.0;
|
||||
state->e.peak = 1.0;
|
||||
state->e.envelope = 1.0;
|
||||
state->c.step_up = pow(COMPRESS_ATTACK_FACTOR, 1000.0 / attack_ms / samplerate);
|
||||
state->c.step_down = pow(COMPRESS_RECOVERY_FACTOR, 1000.0 / recovery_ms / samplerate);
|
||||
state->e.step_up = pow(EXPAND_ATTACK_FACTOR, 1000.0 / attack_ms / samplerate);
|
||||
state->e.step_down = pow(EXPAND_RECOVERY_FACTOR, 1000.0 / recovery_ms / samplerate);
|
||||
|
||||
// FIXME: make global, not at instance
|
||||
for (i = 0; i < 10000; i++)
|
||||
sqrt_tab[i] = sqrt(i * 0.001);
|
||||
}
|
||||
|
||||
void compress_audio(compandor_t *state, sample_t *samples, int num)
|
||||
{
|
||||
double value, peak, envelope, step_up, step_down;
|
||||
int i;
|
||||
|
||||
step_up = state->c.step_up;
|
||||
step_down = state->c.step_down;
|
||||
peak = state->c.peak;
|
||||
envelope = state->c.envelope;
|
||||
|
||||
// printf("envelope=%.4f\n", envelope);
|
||||
for (i = 0; i < num; i++) {
|
||||
value = *samples;
|
||||
|
||||
/* 'peak' is the level that raises directly with the signal
|
||||
* level, but falls with specified recovery rate. */
|
||||
if (fabs(value) > peak)
|
||||
peak = fabs(value);
|
||||
else
|
||||
peak *= step_down;
|
||||
/* 'evelope' is the level that raises with the specified attack
|
||||
* rate to 'peak', but falls with specified recovery rate. */
|
||||
if (peak > envelope)
|
||||
envelope *= step_up;
|
||||
else
|
||||
envelope = peak;
|
||||
if (envelope < ENVELOPE_MIN)
|
||||
envelope = ENVELOPE_MIN;
|
||||
if (envelope > ENVELOPE_MAX)
|
||||
envelope = ENVELOPE_MAX;
|
||||
|
||||
*samples++ = value / sqrt_tab[(int)(envelope / 0.001)];
|
||||
//if (i > 47000.0 && i < 48144)
|
||||
//printf("time=%.4f envelope=%.4fdb, value=%.4f\n", (double)i/48000.0, 20*log10(envelope), value);
|
||||
}
|
||||
//exit(0);
|
||||
|
||||
state->c.envelope = envelope;
|
||||
state->c.peak = peak;
|
||||
}
|
||||
|
||||
void expand_audio(compandor_t *state, sample_t *samples, int num)
|
||||
{
|
||||
double value, peak, envelope, step_up, step_down;
|
||||
int i;
|
||||
|
||||
step_up = state->e.step_up;
|
||||
step_down = state->e.step_down;
|
||||
peak = state->e.peak;
|
||||
envelope = state->e.envelope;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
value = *samples;
|
||||
|
||||
/* for comments: see compress_audio() */
|
||||
if (fabs(value) > peak)
|
||||
peak = fabs(value);
|
||||
else
|
||||
peak *= step_down;
|
||||
if (peak > envelope)
|
||||
envelope *= step_up;
|
||||
else
|
||||
envelope = peak;
|
||||
if (envelope < ENVELOPE_MIN)
|
||||
envelope = ENVELOPE_MIN;
|
||||
|
||||
*samples++ = value * envelope;
|
||||
}
|
||||
|
||||
state->e.envelope = envelope;
|
||||
state->e.peak = peak;
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
|
||||
typedef struct compandor {
|
||||
struct {
|
||||
double step_up;
|
||||
double step_down;
|
||||
double peak;
|
||||
double envelope;
|
||||
} c;
|
||||
struct {
|
||||
double step_up;
|
||||
double step_down;
|
||||
double peak;
|
||||
double envelope;
|
||||
} e;
|
||||
} compandor_t;
|
||||
|
||||
void init_compandor(compandor_t *state, double samplerate, double attack_ms, double recovery_ms);
|
||||
void compress_audio(compandor_t *state, sample_t *samples, int num);
|
||||
void expand_audio(compandor_t *state, sample_t *samples, int num);
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libdebug.a
|
||||
|
||||
libdebug_a_SOURCES = \
|
||||
debug.c
|
||||
|
|
@ -1,325 +0,0 @@
|
|||
/* 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 <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include "debug.h"
|
||||
|
||||
const char *debug_level[] = {
|
||||
"debug ",
|
||||
"info ",
|
||||
"notice ",
|
||||
"error ",
|
||||
NULL,
|
||||
};
|
||||
|
||||
struct debug_cat {
|
||||
const char *name;
|
||||
const char *color;
|
||||
} debug_cat[] = {
|
||||
{ "options", "\033[0;33m" },
|
||||
{ "sender", "\033[1;33m" },
|
||||
{ "sound", "\033[0;35m" },
|
||||
{ "dsp", "\033[0;31m" },
|
||||
{ "anetz", "\033[1;34m" },
|
||||
{ "bnetz", "\033[1;34m" },
|
||||
{ "cnetz", "\033[1;34m" },
|
||||
{ "nmt", "\033[1;34m" },
|
||||
{ "amps", "\033[1;34m" },
|
||||
{ "r2000", "\033[1;34m" },
|
||||
{ "imts", "\033[1;34m" },
|
||||
{ "mpt1327", "\033[1;34m" },
|
||||
{ "jollycom", "\033[1;34m" },
|
||||
{ "eurosignal", "\033[1;34m" },
|
||||
{ "pocsag", "\033[1;34m" },
|
||||
{ "5-ton-folge", "\033[1;34m" },
|
||||
{ "frame", "\033[0;36m" },
|
||||
{ "call", "\033[0;37m" },
|
||||
{ "cc", "\033[1;32m" },
|
||||
{ "database", "\033[0;33m" },
|
||||
{ "transaction", "\033[0;32m" },
|
||||
{ "dms", "\033[0;33m" },
|
||||
{ "sms", "\033[1;37m" },
|
||||
{ "sdr", "\033[1;31m" },
|
||||
{ "uhd", "\033[1;35m" },
|
||||
{ "soapy", "\033[1;35m" },
|
||||
{ "wave", "\033[1;33m" },
|
||||
{ "radio", "\033[1;34m" },
|
||||
{ "am791x", "\033[0;31m" },
|
||||
{ "uart", "\033[0;32m" },
|
||||
{ "device", "\033[0;33m" },
|
||||
{ "datenklo", "\033[1;34m" },
|
||||
{ "zeit", "\033[1;34m" },
|
||||
{ "sim layer 1", "\033[0;31m" },
|
||||
{ "sim layer 2", "\033[0;33m" },
|
||||
{ "sim ICL layer", "\033[0;36m" },
|
||||
{ "sim layer 7", "\033[0;37m" },
|
||||
{ "mtp layer 2", "\033[1;33m" },
|
||||
{ "mtp layer 3", "\033[1;36m" },
|
||||
{ "MuP", "\033[1;37m" },
|
||||
{ "router", "\033[1;35m" },
|
||||
{ "stderr", "\033[1;37m" },
|
||||
{ "ss5", "\033[1;34m" },
|
||||
{ "isdn", "\033[1;35m" },
|
||||
{ "misdn", "\033[0;34m" },
|
||||
{ "dss1", "\033[1;34m" },
|
||||
{ "sip", "\033[1;35m" },
|
||||
{ "telephone", "\033[1;34m" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int debuglevel = DEBUG_INFO;
|
||||
int debug_date = 0;
|
||||
uint64_t debug_mask = ~0;
|
||||
extern int num_kanal;
|
||||
|
||||
void (*clear_console_text)(void) = NULL;
|
||||
void (*print_console_text)(void) = NULL;
|
||||
|
||||
int debug_limit_scroll = 0;
|
||||
|
||||
static int lock_initialized = 0;
|
||||
static pthread_mutex_t debug_mutex;
|
||||
|
||||
void lock_debug(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!lock_initialized) {
|
||||
rc = pthread_mutex_init(&debug_mutex, NULL);
|
||||
if (rc == 0)
|
||||
lock_initialized = 1;
|
||||
}
|
||||
if (lock_initialized)
|
||||
pthread_mutex_lock(&debug_mutex);
|
||||
}
|
||||
|
||||
void unlock_debug(void)
|
||||
{
|
||||
if (lock_initialized)
|
||||
pthread_mutex_unlock(&debug_mutex);
|
||||
}
|
||||
|
||||
void get_win_size(int *w, int *h)
|
||||
{
|
||||
struct winsize win;
|
||||
int rc;
|
||||
|
||||
rc = ioctl(0, TIOCGWINSZ, &win);
|
||||
if (rc) {
|
||||
*w = 80;
|
||||
*h = 25;
|
||||
return;
|
||||
}
|
||||
|
||||
*h = win.ws_row;
|
||||
*w = win.ws_col;
|
||||
}
|
||||
|
||||
void _printdebug(const char *file, const char __attribute__((unused)) *function, int line, int cat, int level, const char *kanal, const char *fmt, ...)
|
||||
{
|
||||
char buffer[4096], *b = buffer;
|
||||
int s = sizeof(buffer) - 1;
|
||||
const char *p;
|
||||
va_list args;
|
||||
int w, h;
|
||||
|
||||
if (debuglevel > level)
|
||||
return;
|
||||
|
||||
if (!(debug_mask & ((uint64_t)1 << cat)))
|
||||
return;
|
||||
|
||||
lock_debug();
|
||||
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
|
||||
/* if kanal is used, prefix the channel number */
|
||||
if (num_kanal > 1 && kanal) {
|
||||
sprintf(buffer, "(chan %s) ", kanal);
|
||||
b = strchr(buffer, '\0');
|
||||
s -= strlen(buffer);
|
||||
}
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(b, s, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
while ((p = strchr(file, '/')))
|
||||
file = p + 1;
|
||||
if (clear_console_text)
|
||||
clear_console_text();
|
||||
if (debug_limit_scroll) {
|
||||
get_win_size(&w, &h);
|
||||
printf("\0337\033[%d;%dr\0338", debug_limit_scroll + 1, h);
|
||||
}
|
||||
if (debug_date) {
|
||||
struct timeval tv;
|
||||
struct tm *tm;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
tm = localtime(&tv.tv_sec);
|
||||
|
||||
printf("%04d-%02d-%02d %02d:%02d:%02d.%03d ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)(tv.tv_usec / 10000.0));
|
||||
}
|
||||
printf("%s%s:%4d %s: %s\033[0;39m", debug_cat[cat].color, file, line, debug_level[level], buffer);
|
||||
if (debug_limit_scroll)
|
||||
printf("\0337\033[%d;%dr\0338", 1, h);
|
||||
if (print_console_text)
|
||||
print_console_text();
|
||||
fflush(stdout);
|
||||
|
||||
unlock_debug();
|
||||
}
|
||||
|
||||
const char *debug_amplitude(double level)
|
||||
{
|
||||
static char text[42];
|
||||
|
||||
strcpy(text, " : ");
|
||||
if (level > 1.0)
|
||||
level = 1.0;
|
||||
if (level < -1.0)
|
||||
level = -1.0;
|
||||
text[20 + (int)(level * 20)] = '*';
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
#define level2db(level) (20 * log10(level))
|
||||
|
||||
const char *debug_db(double level_db)
|
||||
{
|
||||
static char text[128];
|
||||
int l;
|
||||
|
||||
strcpy(text, ": . : . : . : . : . : . : . : . | . : . : . : . : . : . : . : . :");
|
||||
if (level_db <= 0.0)
|
||||
return text;
|
||||
l = (int)round(level2db(level_db));
|
||||
if (l > 48)
|
||||
return text;
|
||||
if (l < -48)
|
||||
return text;
|
||||
text[l + 48] = '*';
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
void debug_print_help(void)
|
||||
{
|
||||
printf(" -v --verbose <level> | <level>,<category>[,<category>[,...]] | list\n");
|
||||
printf(" Use 'list' to get a list of all levels and categories\n");
|
||||
printf(" Verbose level: digit of debug level (default = '%d')\n", debuglevel);
|
||||
printf(" Verbose level+category: level digit followed by one or more categories\n");
|
||||
printf(" -> If no category is specified, all categories are selected\n");
|
||||
printf(" -v --verbose date\n");
|
||||
printf(" Show date with debug output\n");
|
||||
}
|
||||
|
||||
void debug_list_cat(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
printf("Give number of debug level:\n");
|
||||
for (i = 0; debug_level[i]; i++)
|
||||
printf(" %d = %s\n", i, debug_level[i]);
|
||||
printf("\n");
|
||||
|
||||
printf("Give name(s) of debug category:\n");
|
||||
for (i = 0; debug_cat[i].name; i++)
|
||||
printf(" %s%s\033[0;39m\n", debug_cat[i].color, debug_cat[i].name);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int parse_debug_opt(const char *optarg)
|
||||
{
|
||||
int i, max_level = 0;
|
||||
char *dup, *dstring, *p;
|
||||
|
||||
if (!strcasecmp(optarg, "date")) {
|
||||
debug_date = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; debug_level[i]; i++)
|
||||
max_level = i;
|
||||
|
||||
dup = dstring = strdup(optarg);
|
||||
p = strsep(&dstring, ",");
|
||||
for (i = 0; i < p[i]; i++) {
|
||||
if (p[i] < '0' || p[i] > '9') {
|
||||
fprintf(stderr, "Only digits are allowed for debug level!\n");
|
||||
free(dup);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
debuglevel = atoi(p);
|
||||
if (debuglevel > max_level) {
|
||||
fprintf(stderr, "Debug level too high, use 'list' to show available levels!\n");
|
||||
free(dup);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (dstring)
|
||||
debug_mask = 0;
|
||||
while((p = strsep(&dstring, ","))) {
|
||||
for (i = 0; debug_cat[i].name; i++) {
|
||||
if (!strcasecmp(p, debug_cat[i].name))
|
||||
break;
|
||||
}
|
||||
if (!debug_cat[i].name) {
|
||||
fprintf(stderr, "Given debug category '%s' unknown, use 'list' to show available categories!\n", p);
|
||||
free(dup);
|
||||
return -EINVAL;
|
||||
}
|
||||
debug_mask |= ((uint64_t)1 << i);
|
||||
}
|
||||
|
||||
free(dup);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *debug_hex(const uint8_t *data, int len)
|
||||
{
|
||||
static char *text = NULL;
|
||||
char *p;
|
||||
int i;
|
||||
|
||||
if (text)
|
||||
free(text);
|
||||
p = text = calloc(1, len * 3 + 1);
|
||||
for (i = 0; i < len; i++) {
|
||||
sprintf(p, "%02x ", *data++);
|
||||
p += 3;
|
||||
}
|
||||
if (text[0])
|
||||
p[-1] = '\0';
|
||||
|
||||
return text;
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
|
||||
#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 DOPTIONS 0
|
||||
#define DSENDER 1
|
||||
#define DSOUND 2
|
||||
#define DDSP 3
|
||||
#define DANETZ 4
|
||||
#define DBNETZ 5
|
||||
#define DCNETZ 6
|
||||
#define DNMT 7
|
||||
#define DAMPS 8
|
||||
#define DR2000 9
|
||||
#define DIMTS 10
|
||||
#define DMPT1327 11
|
||||
#define DJOLLY 12
|
||||
#define DEURO 13
|
||||
#define DPOCSAG 14
|
||||
#define DFUENF 15
|
||||
#define DFRAME 16
|
||||
#define DCALL 17
|
||||
#define DCC 18
|
||||
#define DDB 19
|
||||
#define DTRANS 20
|
||||
#define DDMS 21
|
||||
#define DSMS 22
|
||||
#define DSDR 23
|
||||
#define DUHD 24
|
||||
#define DSOAPY 25
|
||||
#define DWAVE 26
|
||||
#define DRADIO 27
|
||||
#define DAM791X 28
|
||||
#define DUART 29
|
||||
#define DDEVICE 30
|
||||
#define DDATENKLO 31
|
||||
#define DZEIT 32
|
||||
#define DSIM1 33
|
||||
#define DSIM2 34
|
||||
#define DSIMI 35
|
||||
#define DSIM7 36
|
||||
#define DMTP2 37
|
||||
#define DMTP3 38
|
||||
#define DMUP 39
|
||||
#define DROUTER 40
|
||||
#define DSTDERR 41
|
||||
#define DSS5 42
|
||||
#define DISDN 43
|
||||
#define DMISDN 44
|
||||
#define DDSS1 45
|
||||
#define DSIP 46
|
||||
#define DTEL 47
|
||||
|
||||
void lock_debug(void);
|
||||
void unlock_debug(void);
|
||||
|
||||
void get_win_size(int *w, int *h);
|
||||
|
||||
#define PDEBUG(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, NULL, fmt, ## arg)
|
||||
#define PDEBUG_CHAN(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, CHAN, fmt, ## arg)
|
||||
void _printdebug(const char *file, const char *function, int line, int cat, int level, const char *chan_str, const char *fmt, ...) __attribute__ ((__format__ (__printf__, 7, 8)));
|
||||
|
||||
const char *debug_amplitude(double level);
|
||||
const char *debug_db(double level_db);
|
||||
|
||||
void debug_print_help(void);
|
||||
void debug_list_cat(void);
|
||||
int parse_debug_opt(const char *opt);
|
||||
|
||||
extern int debuglevel;
|
||||
|
||||
extern void (*clear_console_text)(void);
|
||||
extern void (*print_console_text)(void);
|
||||
|
||||
extern int debug_limit_scroll;
|
||||
|
||||
const char *debug_hex(const uint8_t *data, int len);
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libdisplay.a
|
||||
|
||||
libdisplay_a_SOURCES = \
|
||||
display_status.c \
|
||||
display_wave.c \
|
||||
display_measurements.c
|
||||
|
||||
if HAVE_SDR
|
||||
libdisplay_a_SOURCES += \
|
||||
display_iq.c \
|
||||
display_spectrum.c
|
||||
endif
|
||||
|
||||
if HAVE_SDR
|
||||
AM_CPPFLAGS += -DHAVE_SDR
|
||||
endif
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
#define DISPLAY_MEAS_INTERVAL 0.1 /* time (in seconds) for each measurement values interval */
|
||||
#define DISPLAY_INTERVAL 0.04 /* time (in seconds) for each other interval */
|
||||
#define DISPLAY_PARAM_HISTORIES 10 /* number of intervals (should result in one seconds) */
|
||||
|
||||
#define MAX_DISPLAY_WIDTH 1024
|
||||
|
||||
typedef struct display_wave {
|
||||
const char *kanal;
|
||||
int interval_pos;
|
||||
int interval_max;
|
||||
int offset;
|
||||
sample_t buffer[MAX_DISPLAY_WIDTH];
|
||||
} dispwav_t;
|
||||
|
||||
enum display_measurements_type {
|
||||
DISPLAY_MEAS_LAST, /* display last value */
|
||||
DISPLAY_MEAS_PEAK, /* display peak value */
|
||||
DISPLAY_MEAS_PEAK2PEAK, /* display peak value of min..max range */
|
||||
DISPLAY_MEAS_AVG, /* display average value */
|
||||
};
|
||||
|
||||
enum display_measurements_bar {
|
||||
DISPLAY_MEAS_LEFT, /* bar graph from left */
|
||||
DISPLAY_MEAS_CENTER, /* bar graph from center */
|
||||
};
|
||||
|
||||
typedef struct display_measurements_param {
|
||||
struct display_measurements_param *next;
|
||||
char name[32]; /* parameter name (e.g. 'Deviation') */
|
||||
char format[32]; /* unit name (e.g. "%.2f KHz") */
|
||||
enum display_measurements_type type;
|
||||
enum display_measurements_bar bar;
|
||||
double min; /* minimum value */
|
||||
double max; /* maximum value */
|
||||
double mark; /* mark (target) value */
|
||||
double value; /* current value (peak, sum...) */
|
||||
double value2; /* max value for min..max range */
|
||||
double last; /* last valid value (used for DISPLAY_MEAS_LAST) */
|
||||
int value_count; /* count number of values of one interval */
|
||||
double value_history[DISPLAY_PARAM_HISTORIES]; /* history of values of last second */
|
||||
double value2_history[DISPLAY_PARAM_HISTORIES]; /* stores max for min..max range */
|
||||
int value_history_pos; /* next history value to write */
|
||||
} dispmeasparam_t;
|
||||
|
||||
typedef struct display_measurements {
|
||||
struct display_measurements *next;
|
||||
const char *kanal;
|
||||
dispmeasparam_t *param;
|
||||
} dispmeas_t;
|
||||
|
||||
#define MAX_DISPLAY_IQ 1024
|
||||
|
||||
typedef struct display_iq {
|
||||
int interval_pos;
|
||||
int interval_max;
|
||||
float buffer[MAX_DISPLAY_IQ * 2];
|
||||
} dispiq_t;
|
||||
|
||||
#define MAX_DISPLAY_SPECTRUM 1024
|
||||
|
||||
typedef struct display_spectrum_mark {
|
||||
struct display_spectrum_mark *next;
|
||||
const char *kanal;
|
||||
double frequency;
|
||||
} dispspectrum_mark_t;
|
||||
|
||||
typedef struct display_spectrum {
|
||||
int interval_pos;
|
||||
int interval_max;
|
||||
double buffer_I[MAX_DISPLAY_SPECTRUM];
|
||||
double buffer_Q[MAX_DISPLAY_SPECTRUM];
|
||||
dispspectrum_mark_t *mark;
|
||||
} dispspectrum_t;
|
||||
|
||||
#define MAX_HEIGHT_STATUS 32
|
||||
|
||||
void display_wave_init(dispwav_t *disp, int samplerate, const char *kanal);
|
||||
void display_wave_on(int on);
|
||||
void display_wave(dispwav_t *disp, sample_t *samples, int length, double range);
|
||||
|
||||
void display_status_on(int on);
|
||||
void display_status_start(void);
|
||||
void display_status_channel(const char *kanal, const char *type, const char *state);
|
||||
void display_status_subscriber(const char *number, const char *state);
|
||||
void display_status_end(void);
|
||||
|
||||
void display_measurements_init(dispmeas_t *disp, int samplerate, const char *kanal);
|
||||
void display_measurements_exit(dispmeas_t *disp);
|
||||
void display_measurements_on(int on);
|
||||
dispmeasparam_t *display_measurements_add(dispmeas_t *disp, char *name, char *format, enum display_measurements_type type, enum display_measurements_bar bar, double min, double max, double mark);
|
||||
void display_measurements_update(dispmeasparam_t *param, double value, double value2);
|
||||
void display_measurements(double elapsed);
|
||||
|
||||
void display_iq_init(int samplerate);
|
||||
void display_iq_on(int on);
|
||||
void display_iq(float *samples, int length);
|
||||
|
||||
void display_spectrum_init(int samplerate, double center_frequency);
|
||||
void display_spectrum_add_mark(const char *kanal, double frequency);
|
||||
void display_spectrum_exit(void);
|
||||
void display_spectrum_on(int on);
|
||||
void display_spectrum(float *samples, int length);
|
||||
|
|
@ -1,282 +0,0 @@
|
|||
/* display IQ data form 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 <string.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libdisplay/display.h"
|
||||
|
||||
/* must be odd value! */
|
||||
#define SIZE 23
|
||||
|
||||
static char screen[SIZE][MAX_DISPLAY_WIDTH];
|
||||
static uint8_t screen_color[SIZE][MAX_DISPLAY_WIDTH];
|
||||
static uint8_t screen_history[SIZE * 2][MAX_DISPLAY_WIDTH];
|
||||
static int iq_on = 0;
|
||||
static double db = 80;
|
||||
|
||||
static dispiq_t disp;
|
||||
|
||||
void display_iq_init(int samplerate)
|
||||
{
|
||||
memset(&disp, 0, sizeof(disp));
|
||||
memset(&screen_history, 0, sizeof(screen_history));
|
||||
disp.interval_max = (double)samplerate * DISPLAY_INTERVAL + 0.5;
|
||||
/* should not happen due to low interval */
|
||||
if (disp.interval_max < MAX_DISPLAY_IQ - 1)
|
||||
disp.interval_max = MAX_DISPLAY_IQ - 1;
|
||||
}
|
||||
|
||||
void display_iq_on(int on)
|
||||
{
|
||||
int j;
|
||||
int w, h;
|
||||
|
||||
get_win_size(&w, &h);
|
||||
if (w > MAX_DISPLAY_WIDTH - 1)
|
||||
w = MAX_DISPLAY_WIDTH - 1;
|
||||
|
||||
if (iq_on) {
|
||||
memset(&screen, ' ', sizeof(screen));
|
||||
memset(&screen_history, 0, sizeof(screen_history));
|
||||
lock_debug();
|
||||
printf("\0337\033[H");
|
||||
for (j = 0; j < SIZE; j++) {
|
||||
screen[j][w] = '\0';
|
||||
puts(screen[j]);
|
||||
}
|
||||
printf("\0338"); fflush(stdout);
|
||||
unlock_debug();
|
||||
}
|
||||
|
||||
if (on < 0) {
|
||||
if (++iq_on == 3)
|
||||
iq_on = 0;
|
||||
} else
|
||||
iq_on = on;
|
||||
|
||||
if (iq_on)
|
||||
debug_limit_scroll = SIZE;
|
||||
else
|
||||
debug_limit_scroll = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* plot IQ data:
|
||||
*
|
||||
* theoretical example: SIZE = 3 allows 6 steps plotted as dots
|
||||
*
|
||||
* Line 0: :
|
||||
* Line 1: :
|
||||
* Line 2: :
|
||||
*
|
||||
* The level of -1.0 .. 1.0 is scaled to -3 and 3.
|
||||
*
|
||||
* The lowest of the upper 3 dots ranges from 0.0 .. <1.5.
|
||||
* The upper most dot ranges from 2.5 .. <3.5.
|
||||
* The highest of the lower 3 dots ranges from <0.0 .. >-1.5;
|
||||
* The lower most dot ranges from -2.5 .. >-3.5.
|
||||
*
|
||||
* The center column ranges from -0.5 .. <0.5.
|
||||
* The columns about the center from -1.5 .. <1.5.
|
||||
*/
|
||||
void display_iq(float *samples, int length)
|
||||
{
|
||||
int pos, max;
|
||||
float *buffer;
|
||||
int i, j, k;
|
||||
int color = 9; /* default color */
|
||||
int x_center, y_center;
|
||||
double I, Q, L, l, s;
|
||||
int x, y;
|
||||
int v, r;
|
||||
int width, h;
|
||||
|
||||
if (!iq_on)
|
||||
return;
|
||||
|
||||
lock_debug();
|
||||
|
||||
get_win_size(&width, &h);
|
||||
if (width > MAX_DISPLAY_WIDTH - 1)
|
||||
width = MAX_DISPLAY_WIDTH - 1;
|
||||
|
||||
/* at what line we draw our zero-line and what character we use */
|
||||
x_center = width >> 1;
|
||||
y_center = (SIZE - 1) >> 1;
|
||||
|
||||
pos = disp.interval_pos;
|
||||
max = disp.interval_max;
|
||||
buffer = disp.buffer;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
if (pos >= MAX_DISPLAY_IQ) {
|
||||
if (++pos == max)
|
||||
pos = 0;
|
||||
continue;
|
||||
}
|
||||
buffer[pos * 2] = samples[i * 2];
|
||||
buffer[pos * 2 + 1] = samples[i * 2 + 1];
|
||||
pos++;
|
||||
if (pos == MAX_DISPLAY_IQ) {
|
||||
memset(&screen, ' ', sizeof(screen));
|
||||
memset(&screen_color, 7, sizeof(screen_color));
|
||||
/* render screen history to screen */
|
||||
for (y = 0; y < SIZE * 2; y++) {
|
||||
for (x = 0; x < width; x++) {
|
||||
v = screen_history[y][x];
|
||||
v -= 8;
|
||||
if (v < 0)
|
||||
v = 0;
|
||||
screen_history[y][x] = v;
|
||||
r = random() & 0x3f;
|
||||
if (r >= v)
|
||||
continue;
|
||||
if (screen[y/2][x] == ':')
|
||||
continue;
|
||||
if (screen[y/2][x] == '.') {
|
||||
if ((y & 1) == 0)
|
||||
screen[y/2][x] = ':';
|
||||
continue;
|
||||
}
|
||||
if (screen[y/2][x] == '\'') {
|
||||
if ((y & 1))
|
||||
screen[y/2][x] = ':';
|
||||
continue;
|
||||
}
|
||||
if ((y & 1) == 0)
|
||||
screen[y/2][x] = '\'';
|
||||
else
|
||||
screen[y/2][x] = '.';
|
||||
screen_color[y/2][x] = 4;
|
||||
}
|
||||
}
|
||||
/* plot current IQ date */
|
||||
for (j = 0; j < MAX_DISPLAY_IQ; j++) {
|
||||
I = buffer[j * 2];
|
||||
Q = buffer[j * 2 + 1];
|
||||
L = I*I + Q*Q;
|
||||
if (iq_on > 1) {
|
||||
/* logarithmic scale */
|
||||
l = sqrt(L);
|
||||
s = log10(l) * 20 + db;
|
||||
if (s < 0)
|
||||
s = 0;
|
||||
I = (I / l) * (s / db);
|
||||
Q = (Q / l) * (s / db);
|
||||
}
|
||||
x = x_center + (int)(I * (double)SIZE + (double)width + 0.5) - width;
|
||||
if (x < 0)
|
||||
continue;
|
||||
if (x > width - 1)
|
||||
continue;
|
||||
if (Q >= 0)
|
||||
y = SIZE - 1 - (int)(Q * (double)SIZE - 0.5);
|
||||
else
|
||||
y = SIZE - (int)(Q * (double)SIZE + 0.5);
|
||||
if (y < 0)
|
||||
continue;
|
||||
if (y > SIZE * 2 - 1)
|
||||
continue;
|
||||
if (screen[y/2][x] == ':' && screen_color[y/2][x] >= 10)
|
||||
goto cont;
|
||||
if (screen[y/2][x] == '.' && screen_color[y/2][x] >= 10) {
|
||||
if ((y & 1) == 0)
|
||||
screen[y/2][x] = ':';
|
||||
goto cont;
|
||||
}
|
||||
if (screen[y/2][x] == '\'' && screen_color[y/2][x] >= 10) {
|
||||
if ((y & 1))
|
||||
screen[y/2][x] = ':';
|
||||
goto cont;
|
||||
}
|
||||
if ((y & 1) == 0)
|
||||
screen[y/2][x] = '\'';
|
||||
else
|
||||
screen[y/2][x] = '.';
|
||||
cont:
|
||||
screen_history[y][x] = 255;
|
||||
/* overdrive:
|
||||
* red = close to -1..1 or above
|
||||
* yellow = close to -0.5..0.5 or above
|
||||
* Note: L is square of vector length,
|
||||
* so we compare with square values.
|
||||
*/
|
||||
if (L > 0.9 * 0.9)
|
||||
screen_color[y/2][x] = 11;
|
||||
else if (L > 0.45 * 0.45 && screen_color[y/2][x] != 11)
|
||||
screen_color[y/2][x] = 13;
|
||||
else if (screen_color[y/2][x] < 10)
|
||||
screen_color[y/2][x] = 12;
|
||||
}
|
||||
if (iq_on == 1)
|
||||
sprintf(screen[0], "(IQ linear");
|
||||
else
|
||||
sprintf(screen[0], "(IQ log %.0f dB", db);
|
||||
*strchr(screen[0], '\0') = ')';
|
||||
printf("\0337\033[H");
|
||||
for (j = 0; j < SIZE; j++) {
|
||||
for (k = 0; k < width; k++) {
|
||||
if ((j == y_center || k == x_center) && screen[j][k] == ' ') {
|
||||
/* cross */
|
||||
if (color != 4) {
|
||||
color = 4;
|
||||
printf("\033[0;34m");
|
||||
}
|
||||
if (j == y_center) {
|
||||
if (k == x_center)
|
||||
putchar('o');
|
||||
else if (k == x_center - SIZE)
|
||||
putchar('+');
|
||||
else if (k == x_center + SIZE)
|
||||
putchar('+');
|
||||
else
|
||||
putchar('-');
|
||||
} else {
|
||||
if (j == 0 || j == SIZE - 1)
|
||||
putchar('+');
|
||||
else
|
||||
putchar('|');
|
||||
}
|
||||
} else {
|
||||
if (screen_color[j][k] != color) {
|
||||
color = screen_color[j][k];
|
||||
printf("\033[%d;3%dm", color / 10, color % 10);
|
||||
}
|
||||
putchar(screen[j][k]);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
/* reset color and position */
|
||||
printf("\033[0;39m\0338"); fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
disp.interval_pos = pos;
|
||||
|
||||
unlock_debug();
|
||||
}
|
||||
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
/* display measurements functions
|
||||
*
|
||||
* (C) 2017 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 <pthread.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libdisplay/display.h"
|
||||
|
||||
#define MAX_NAME_LEN 16
|
||||
#define MAX_UNIT_LEN 16
|
||||
|
||||
static int has_init = 0;
|
||||
static int measurements_on = 0;
|
||||
double time_elapsed = 0.0;
|
||||
static int lines_total = 0;
|
||||
static char line[MAX_DISPLAY_WIDTH];
|
||||
static char line_color[MAX_DISPLAY_WIDTH];
|
||||
|
||||
dispmeas_t *meas_head = NULL;
|
||||
|
||||
void display_measurements_init(dispmeas_t *disp, int __attribute__((unused)) samplerate, const char *kanal)
|
||||
{
|
||||
dispmeas_t **disp_p;
|
||||
|
||||
memset(disp, 0, sizeof(*disp));
|
||||
disp->kanal = kanal;
|
||||
has_init = 1;
|
||||
lines_total = 0;
|
||||
time_elapsed = 0.0;
|
||||
|
||||
disp_p = &meas_head;
|
||||
while (*disp_p)
|
||||
disp_p = &((*disp_p)->next);
|
||||
*disp_p = disp;
|
||||
}
|
||||
|
||||
void display_measurements_exit(dispmeas_t *disp)
|
||||
{
|
||||
dispmeasparam_t *param = disp->param, *temp;
|
||||
|
||||
while (param) {
|
||||
temp = param;
|
||||
param = param->next;
|
||||
free(temp);
|
||||
}
|
||||
disp->param = NULL;
|
||||
has_init = 0;
|
||||
}
|
||||
|
||||
static int color;
|
||||
|
||||
static void display_line(int on, int w)
|
||||
{
|
||||
int j;
|
||||
|
||||
if (on) {
|
||||
for (j = 0; j < w; j++) {
|
||||
if (line_color[j] != color && line[j] != ' ') {
|
||||
color = line_color[j];
|
||||
printf("\033[%d;3%dm", color / 10, color % 10);
|
||||
}
|
||||
putchar(line[j]);
|
||||
}
|
||||
} else {
|
||||
for (j = 0; j < w; j++)
|
||||
putchar(' ');
|
||||
}
|
||||
putchar('\n');
|
||||
lines_total++;
|
||||
}
|
||||
|
||||
static void print_measurements(int on)
|
||||
{
|
||||
dispmeas_t *disp;
|
||||
dispmeasparam_t *param;
|
||||
int i, j;
|
||||
int width, h;
|
||||
char text[128];
|
||||
double value = 0.0, value2 = 0.0, hold, hold2;
|
||||
int bar_width, bar_left, bar_right, bar_hold, bar_mark;
|
||||
|
||||
get_win_size(&width, &h);
|
||||
if (width > MAX_DISPLAY_WIDTH - 1)
|
||||
width = MAX_DISPLAY_WIDTH - 1;
|
||||
|
||||
/* no display, if bar graph is less than one character */
|
||||
bar_width = width - MAX_NAME_LEN - MAX_UNIT_LEN;
|
||||
if (bar_width < 1)
|
||||
return;
|
||||
|
||||
lock_debug();
|
||||
|
||||
lines_total = 0;
|
||||
color = -1;
|
||||
printf("\0337\033[H");
|
||||
for (disp = meas_head; disp; disp = disp->next) {
|
||||
memset(line, ' ', width);
|
||||
memset(line_color, 7, width);
|
||||
sprintf(line, "(chan %s", disp->kanal);
|
||||
*strchr(line, '\0') = ')';
|
||||
display_line(on, width);
|
||||
for (param = disp->param; param; param = param->next) {
|
||||
memset(line, ' ', width);
|
||||
memset(line_color, 7, width);
|
||||
memset(line_color, 3, MAX_NAME_LEN); /* yellow */
|
||||
switch (param->type) {
|
||||
case DISPLAY_MEAS_LAST:
|
||||
value = param->value;
|
||||
param->value = -NAN;
|
||||
break;
|
||||
case DISPLAY_MEAS_PEAK:
|
||||
/* peak value */
|
||||
value = param->value;
|
||||
param->value = -NAN;
|
||||
param->value_count = 0;
|
||||
break;
|
||||
case DISPLAY_MEAS_PEAK2PEAK:
|
||||
/* peak to peak value */
|
||||
value = param->value;
|
||||
value2 = param->value2;
|
||||
param->value = -NAN;
|
||||
param->value2 = -NAN;
|
||||
param->value_count = 0;
|
||||
break;
|
||||
case DISPLAY_MEAS_AVG:
|
||||
/* average value */
|
||||
if (param->value_count)
|
||||
value = param->value / (double)param->value_count;
|
||||
else
|
||||
value = -NAN;
|
||||
param->value = 0.0;
|
||||
param->value_count = 0;
|
||||
break;
|
||||
}
|
||||
/* add current value to history */
|
||||
param->value_history[param->value_history_pos] = value;
|
||||
param->value2_history[param->value_history_pos] = value2;
|
||||
param->value_history_pos = param->value_history_pos % DISPLAY_PARAM_HISTORIES;
|
||||
/* calculate hold values */
|
||||
hold = -NAN;
|
||||
hold2 = -NAN;
|
||||
switch (param->type) {
|
||||
case DISPLAY_MEAS_LAST:
|
||||
/* if we have valid value, we update 'last' */
|
||||
if (!isnan(value)) {
|
||||
param->last = value;
|
||||
hold = value;
|
||||
} else
|
||||
hold = param->last;
|
||||
break;
|
||||
case DISPLAY_MEAS_PEAK:
|
||||
for (i = 0; i < DISPLAY_PARAM_HISTORIES; i++) {
|
||||
if (isnan(param->value_history[i]))
|
||||
continue;
|
||||
if (isnan(hold) || param->value_history[i] > hold)
|
||||
hold = param->value_history[i];
|
||||
}
|
||||
break;
|
||||
case DISPLAY_MEAS_PEAK2PEAK:
|
||||
for (i = 0; i < DISPLAY_PARAM_HISTORIES; i++) {
|
||||
if (isnan(param->value_history[i]))
|
||||
continue;
|
||||
if (isnan(hold) || param->value_history[i] < hold)
|
||||
hold = param->value_history[i];
|
||||
if (isnan(hold2) || param->value2_history[i] > hold2)
|
||||
hold2 = param->value2_history[i];
|
||||
}
|
||||
if (!isnan(hold))
|
||||
hold = hold2 - hold;
|
||||
if (!isnan(value))
|
||||
value = value2 - value;
|
||||
break;
|
||||
case DISPLAY_MEAS_AVG:
|
||||
for (i = 0, j = 0; i < DISPLAY_PARAM_HISTORIES; i++) {
|
||||
if (isnan(param->value_history[i]))
|
||||
continue;
|
||||
if (j == 0)
|
||||
hold = 0.0;
|
||||
hold += param->value_history[i];
|
||||
j++;
|
||||
}
|
||||
if (j)
|
||||
hold /= j;
|
||||
break;
|
||||
}
|
||||
/* "Deviation ::::::::::............ 4.5 KHz" */
|
||||
memcpy(line, param->name, (strlen(param->name) < MAX_NAME_LEN) ? strlen(param->name) : MAX_NAME_LEN);
|
||||
if (isinf(value) || isnan(value)) {
|
||||
bar_left = -1;
|
||||
bar_right = -1;
|
||||
} else if (param->bar == DISPLAY_MEAS_CENTER) {
|
||||
if (value >= 0.0) {
|
||||
bar_left = (-param->min) / (param->max - param->min) * ((double)bar_width - 1.0);
|
||||
bar_right = (value - param->min) / (param->max - param->min) * ((double)bar_width - 1.0);
|
||||
} else {
|
||||
bar_left = (value - param->min) / (param->max - param->min) * ((double)bar_width - 1.0);
|
||||
bar_right = (-param->min) / (param->max - param->min) * ((double)bar_width - 1.0);
|
||||
}
|
||||
} else {
|
||||
bar_left = -1;
|
||||
bar_right = (value - param->min) / (param->max - param->min) * ((double)bar_width - 1.0);
|
||||
}
|
||||
if (isinf(hold) || isnan(hold))
|
||||
bar_hold = -1;
|
||||
else
|
||||
bar_hold = (hold - param->min) / (param->max - param->min) * ((double)bar_width - 1.0);
|
||||
if (isinf(param->mark))
|
||||
bar_mark = -1;
|
||||
else
|
||||
bar_mark = (param->mark - param->min) / (param->max - param->min) * ((double)bar_width - 1.0);
|
||||
for (i = 0; i < bar_width; i++) {
|
||||
line[i + MAX_NAME_LEN] = ':';
|
||||
if (i == bar_hold)
|
||||
line_color[i + MAX_NAME_LEN] = 13;
|
||||
else if (i == bar_mark)
|
||||
line_color[i + MAX_NAME_LEN] = 14;
|
||||
else if (i >= bar_left && i <= bar_right)
|
||||
line_color[i + MAX_NAME_LEN] = 2;
|
||||
else
|
||||
line_color[i + MAX_NAME_LEN] = 4;
|
||||
}
|
||||
sprintf(text, param->format, hold);
|
||||
if (isnan(hold))
|
||||
memset(line_color + width - MAX_UNIT_LEN, 4, MAX_UNIT_LEN); /* blue */
|
||||
else
|
||||
memset(line_color + width - MAX_UNIT_LEN, 3, MAX_UNIT_LEN); /* yellow */
|
||||
strncpy(line + width - MAX_UNIT_LEN + 1, text, (strlen(text) < MAX_UNIT_LEN) ? strlen(text) : MAX_UNIT_LEN);
|
||||
display_line(on, width);
|
||||
}
|
||||
}
|
||||
/* reset color and position */
|
||||
printf("\033[0;39m\0338"); fflush(stdout);
|
||||
|
||||
debug_limit_scroll = lines_total;
|
||||
|
||||
unlock_debug();
|
||||
}
|
||||
|
||||
void display_measurements_on(int on)
|
||||
{
|
||||
if (measurements_on)
|
||||
print_measurements(0);
|
||||
|
||||
if (on < 0)
|
||||
measurements_on = 1 - measurements_on;
|
||||
else
|
||||
measurements_on = on;
|
||||
|
||||
debug_limit_scroll = 0;
|
||||
}
|
||||
|
||||
/* add new parameter on startup to the list of measurements */
|
||||
dispmeasparam_t *display_measurements_add(dispmeas_t *disp, char *name, char *format, enum display_measurements_type type, enum display_measurements_bar bar, double min, double max, double mark)
|
||||
{
|
||||
dispmeasparam_t *param, **param_p = &disp->param;
|
||||
int i;
|
||||
|
||||
if (!has_init) {
|
||||
fprintf(stderr, "Not initialized prior adding measurement, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
while (*param_p)
|
||||
param_p = &((*param_p)->next);
|
||||
*param_p = calloc(sizeof(dispmeasparam_t), 1);
|
||||
if (!*param_p)
|
||||
return NULL;
|
||||
param = *param_p;
|
||||
strncpy(param->name, name, sizeof(param->name) - 1);
|
||||
strncpy(param->format, format, sizeof(param->format) - 1);
|
||||
param->type = type;
|
||||
param->bar = bar;
|
||||
param->min = min;
|
||||
param->max = max;
|
||||
param->mark = mark;
|
||||
param->value = -NAN;
|
||||
param->value2 = -NAN;
|
||||
param->last = -NAN;
|
||||
for (i = 0; i < DISPLAY_PARAM_HISTORIES; i++)
|
||||
param->value_history[i] = -NAN;
|
||||
param->value_count = 0;
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
void display_measurements_update(dispmeasparam_t *param, double value, double value2)
|
||||
{
|
||||
/* special case where we do not have an instance of the parameter */
|
||||
if (!param)
|
||||
return;
|
||||
|
||||
if (!has_init) {
|
||||
fprintf(stderr, "Not initialized prior updating measurement value, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
switch (param->type) {
|
||||
case DISPLAY_MEAS_LAST:
|
||||
param->value = value;
|
||||
break;
|
||||
case DISPLAY_MEAS_PEAK:
|
||||
if (isnan(param->value) || value > param->value)
|
||||
param->value = value;
|
||||
break;
|
||||
case DISPLAY_MEAS_PEAK2PEAK:
|
||||
if (param->value_count == 0 || value < param->value)
|
||||
param->value = value;
|
||||
if (param->value_count == 0 || value2 > param->value2)
|
||||
param->value2 = value2;
|
||||
param->value_count++;
|
||||
break;
|
||||
case DISPLAY_MEAS_AVG:
|
||||
param->value += value;
|
||||
param->value_count++;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Parameter '%s' has unknown type %d, please fix!\n", param->name, param->type);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void display_measurements(double elapsed)
|
||||
{
|
||||
if (!measurements_on)
|
||||
return;
|
||||
|
||||
if (!has_init)
|
||||
return;
|
||||
|
||||
/* count and check if we need to display this time */
|
||||
time_elapsed += elapsed;
|
||||
if (time_elapsed < DISPLAY_MEAS_INTERVAL)
|
||||
return;
|
||||
time_elapsed = fmod(time_elapsed, DISPLAY_MEAS_INTERVAL);
|
||||
|
||||
print_measurements(1);
|
||||
}
|
||||
|
|
@ -1,414 +0,0 @@
|
|||
/* display spectrum of IQ data
|
||||
*
|
||||
* (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 <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libfft/fft.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libdisplay/display.h"
|
||||
|
||||
#define HEIGHT 20
|
||||
|
||||
static int has_init = 0;
|
||||
static double buffer_delay[MAX_DISPLAY_SPECTRUM];
|
||||
static double buffer_hold[MAX_DISPLAY_SPECTRUM];
|
||||
static char screen[HEIGHT][MAX_DISPLAY_WIDTH];
|
||||
static uint8_t screen_color[HEIGHT][MAX_DISPLAY_WIDTH];
|
||||
static int spectrum_on = 0;
|
||||
static double db = 120;
|
||||
static double center_frequency, frequency_range;
|
||||
|
||||
static dispspectrum_t disp;
|
||||
|
||||
void display_spectrum_init(int samplerate, double _center_frequency)
|
||||
{
|
||||
memset(&disp, 0, sizeof(disp));
|
||||
disp.interval_max = (double)samplerate * DISPLAY_INTERVAL + 0.5;
|
||||
/* should not happen due to low interval */
|
||||
if (disp.interval_max < MAX_DISPLAY_SPECTRUM - 1)
|
||||
disp.interval_max = MAX_DISPLAY_SPECTRUM - 1;
|
||||
memset(buffer_delay, 0, sizeof(buffer_delay));
|
||||
|
||||
center_frequency = _center_frequency;
|
||||
frequency_range = (double)samplerate;
|
||||
|
||||
has_init = 1;
|
||||
}
|
||||
|
||||
void display_spectrum_add_mark(const char *kanal, double frequency)
|
||||
{
|
||||
dispspectrum_mark_t *mark, **mark_p;
|
||||
|
||||
if (!has_init)
|
||||
return;
|
||||
|
||||
mark = calloc(1, sizeof(*mark));
|
||||
if (!mark) {
|
||||
fprintf(stderr, "no mem!");
|
||||
abort();
|
||||
}
|
||||
mark->kanal = kanal;
|
||||
mark->frequency = frequency;
|
||||
|
||||
mark_p = &disp.mark;
|
||||
while (*mark_p)
|
||||
mark_p = &((*mark_p)->next);
|
||||
*mark_p = mark;
|
||||
}
|
||||
|
||||
void display_spectrum_exit(void)
|
||||
{
|
||||
dispspectrum_mark_t *mark = disp.mark, *temp;
|
||||
|
||||
while (mark) {
|
||||
temp = mark;
|
||||
mark = mark->next;
|
||||
free(temp);
|
||||
}
|
||||
disp.mark = NULL;
|
||||
has_init = 0;
|
||||
}
|
||||
|
||||
void display_spectrum_on(int on)
|
||||
{
|
||||
int j;
|
||||
int w, h;
|
||||
|
||||
get_win_size(&w, &h);
|
||||
if (w > MAX_DISPLAY_WIDTH - 1)
|
||||
w = MAX_DISPLAY_WIDTH - 1;
|
||||
|
||||
if (spectrum_on) {
|
||||
memset(&screen, ' ', sizeof(screen));
|
||||
memset(&buffer_hold, 0, sizeof(buffer_hold));
|
||||
lock_debug();
|
||||
printf("\0337\033[H");
|
||||
for (j = 0; j < HEIGHT; j++) {
|
||||
screen[j][w] = '\0';
|
||||
puts(screen[j]);
|
||||
}
|
||||
printf("\0338"); fflush(stdout);
|
||||
unlock_debug();
|
||||
}
|
||||
|
||||
if (on < 0) {
|
||||
if (++spectrum_on == 3)
|
||||
spectrum_on = 0;
|
||||
} else
|
||||
spectrum_on = on;
|
||||
|
||||
if (spectrum_on)
|
||||
debug_limit_scroll = HEIGHT;
|
||||
else
|
||||
debug_limit_scroll = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* plot spectrum data:
|
||||
*
|
||||
*/
|
||||
void display_spectrum(float *samples, int length)
|
||||
{
|
||||
dispspectrum_mark_t *mark;
|
||||
char print_channel[32], print_frequency[32];
|
||||
int width, h;
|
||||
int pos, max;
|
||||
double *buffer_I, *buffer_Q;
|
||||
int color = 9; /* default color */
|
||||
int i, j, k, o;
|
||||
double I, Q, v;
|
||||
int s, e, l, n;
|
||||
|
||||
if (!spectrum_on)
|
||||
return;
|
||||
|
||||
lock_debug();
|
||||
|
||||
get_win_size(&width, &h);
|
||||
if (width > MAX_DISPLAY_WIDTH - 1)
|
||||
width = MAX_DISPLAY_WIDTH - 1;
|
||||
|
||||
/* calculate size of FFT */
|
||||
int m, fft_size = 0, fft_taps = 0;
|
||||
for (m = 0; m < 16; m++) {
|
||||
if ((1 << m) > MAX_DISPLAY_SPECTRUM)
|
||||
break;
|
||||
if ((1 << m) <= width) {
|
||||
fft_taps = m;
|
||||
fft_size = 1 << m;
|
||||
}
|
||||
}
|
||||
if (m == 16) {
|
||||
fprintf(stderr, "Size of spectrum is not a power of 2, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
int hold[fft_size], delay[fft_size], current[fft_size];
|
||||
|
||||
pos = disp.interval_pos;
|
||||
max = disp.interval_max;
|
||||
buffer_I = disp.buffer_I;
|
||||
buffer_Q = disp.buffer_Q;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
if (pos >= fft_size) {
|
||||
if (++pos == max)
|
||||
pos = 0;
|
||||
continue;
|
||||
}
|
||||
buffer_I[pos] = samples[i * 2];
|
||||
buffer_Q[pos] = samples[i * 2 + 1];
|
||||
pos++;
|
||||
if (pos == fft_size) {
|
||||
fft_process(1, fft_taps, buffer_I, buffer_Q);
|
||||
k = 0;
|
||||
for (j = 0; j < fft_size; j++) {
|
||||
/* scale result vertically */
|
||||
I = buffer_I[(j + fft_size / 2) % fft_size];
|
||||
Q = buffer_Q[(j + fft_size / 2) % fft_size];
|
||||
v = sqrt(I*I + Q*Q);
|
||||
v = log10(v) * 20 + db;
|
||||
if (v < 0)
|
||||
v = 0;
|
||||
v /= db;
|
||||
/* delayed */
|
||||
buffer_delay[j] -= DISPLAY_INTERVAL / 10.0;
|
||||
if (v > buffer_delay[j])
|
||||
buffer_delay[j] = v;
|
||||
delay[j] = (double)(HEIGHT * 2 - 1) * (1.0 - buffer_delay[j]);
|
||||
if (delay[j] < 0)
|
||||
delay[j] = 0;
|
||||
if (delay[j] >= (HEIGHT * 2))
|
||||
delay[j] = (HEIGHT * 2) - 1;
|
||||
/* hold */
|
||||
if (spectrum_on == 2) {
|
||||
if (v > buffer_hold[j])
|
||||
buffer_hold[j] = v;
|
||||
hold[j] = (double)(HEIGHT * 2 - 1) * (1.0 - buffer_hold[j]);
|
||||
if (hold[j] < 0)
|
||||
hold[j] = 0;
|
||||
if (hold[j] >= (HEIGHT * 2))
|
||||
hold[j] = (HEIGHT * 2) - 1;
|
||||
}
|
||||
/* current */
|
||||
current[j] = (double)(HEIGHT * 2 - 1) * (1.0 - v);
|
||||
if (current[j] < 0)
|
||||
current[j] = 0;
|
||||
if (current[j] >= (HEIGHT * 2))
|
||||
current[j] = (HEIGHT * 2) - 1;
|
||||
}
|
||||
/* plot scaled buffer */
|
||||
memset(&screen, ' ', sizeof(screen));
|
||||
memset(&screen_color, 7, sizeof(screen_color)); /* all white */
|
||||
sprintf(screen[0], "(spectrum log %.0f dB%s", db, (spectrum_on == 2) ? " HOLD" : "");
|
||||
*strchr(screen[0], '\0') = ')';
|
||||
for (j = 2; j < HEIGHT; j += 2) {
|
||||
memset(screen_color[j], 4, 7); /* blue */
|
||||
sprintf(screen[j], "%4.0f dB", -(double)(j+1) * db / (double)(HEIGHT - 1));
|
||||
screen[j][7] = ' ';
|
||||
}
|
||||
o = (width - fft_size) / 2; /* offset from left border */
|
||||
for (j = 0; j < fft_size; j++) {
|
||||
/* show current spectrum in yellow */
|
||||
s = l = n = current[j];
|
||||
/* get last and next value */
|
||||
if (j > 0)
|
||||
l = (current[j - 1] + s) / 2;
|
||||
if (j < fft_size - 1)
|
||||
n = (current[j + 1] + s) / 2;
|
||||
if (s > l && s > n) {
|
||||
/* current value is a minimum */
|
||||
e = s;
|
||||
s = (l < n) ? (l + 1) : (n + 1);
|
||||
} else if (s < l && s < n) {
|
||||
/* current value is a maximum */
|
||||
e = (l > n) ? l : n;
|
||||
} else if (l < n) {
|
||||
/* last value is higher, next value is lower */
|
||||
s = l + 1;
|
||||
e = n;
|
||||
} else if (l > n) {
|
||||
/* last value is lower, next value is higher */
|
||||
s = n + 1;
|
||||
e = l;
|
||||
} else {
|
||||
/* current, last and next values are equal */
|
||||
e = s;
|
||||
}
|
||||
if (s == e) {
|
||||
if ((s & 1) == 0)
|
||||
screen[s >> 1][j + o] = '\'';
|
||||
else
|
||||
screen[s >> 1][j + o] = '.';
|
||||
screen_color[s >> 1][j + o] = 13;
|
||||
} else {
|
||||
if ((s & 1) == 0)
|
||||
screen[s >> 1][j + o] = '|';
|
||||
else
|
||||
screen[s >> 1][j + o] = '.';
|
||||
screen_color[s >> 1][j + o] = 13;
|
||||
if ((e & 1) == 0)
|
||||
screen[e >> 1][j + o] = '\'';
|
||||
else
|
||||
screen[e >> 1][j + o] = '|';
|
||||
screen_color[e >> 1][j + o] = 13;
|
||||
for (k = (s >> 1) + 1; k < (e >> 1); k++) {
|
||||
screen[k][j + o] = '|';
|
||||
screen_color[k][j + o] = 13;
|
||||
}
|
||||
}
|
||||
/* show delayed spectrum in blue */
|
||||
e = s;
|
||||
s = delay[j];
|
||||
if ((s >> 1) < (e >> 1)) {
|
||||
if ((s & 1) == 0)
|
||||
screen[s >> 1][j + o] = '|';
|
||||
else
|
||||
screen[s >> 1][j + o] = '.';
|
||||
screen_color[s >> 1][j + o] = 4;
|
||||
for (k = (s >> 1) + 1; k < (e >> 1); k++) {
|
||||
screen[k][j + o] = '|';
|
||||
screen_color[k][j + o] = 4;
|
||||
}
|
||||
}
|
||||
if (spectrum_on == 2) {
|
||||
/* show hold spectrum in white */
|
||||
s = l = n = hold[j];
|
||||
/* get last and next value */
|
||||
if (j > 0)
|
||||
l = (hold[j - 1] + s) / 2;
|
||||
if (j < fft_size - 1)
|
||||
n = (hold[j + 1] + s) / 2;
|
||||
if (s > l && s > n) {
|
||||
/* hold value is a minimum */
|
||||
e = s;
|
||||
s = (l < n) ? (l + 1) : (n + 1);
|
||||
} else if (s < l && s < n) {
|
||||
/* hold value is a maximum */
|
||||
e = (l > n) ? l : n;
|
||||
} else if (l < n) {
|
||||
/* last value is higher, next value is lower */
|
||||
s = l + 1;
|
||||
e = n;
|
||||
} else if (l > n) {
|
||||
/* last value is lower, next value is higher */
|
||||
s = n + 1;
|
||||
e = l;
|
||||
} else {
|
||||
/* hold, last and next values are equal */
|
||||
e = s;
|
||||
}
|
||||
if (s == e) {
|
||||
if ((s & 1) == 0)
|
||||
screen[s >> 1][j + o] = '\'';
|
||||
else
|
||||
screen[s >> 1][j + o] = '.';
|
||||
screen_color[s >> 1][j + o] = 17;
|
||||
} else {
|
||||
if ((s & 1) == 0)
|
||||
screen[s >> 1][j + o] = '|';
|
||||
else
|
||||
screen[s >> 1][j + o] = '.';
|
||||
screen_color[s >> 1][j + o] = 17;
|
||||
if ((e & 1) == 0)
|
||||
screen[e >> 1][j + o] = '\'';
|
||||
else
|
||||
screen[e >> 1][j + o] = '|';
|
||||
screen_color[e >> 1][j + o] = 17;
|
||||
for (k = (s >> 1) + 1; k < (e >> 1); k++) {
|
||||
screen[k][j + o] = '|';
|
||||
screen_color[k][j + o] = 17;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* add channel positions in spectrum */
|
||||
for (mark = disp.mark; mark; mark = mark->next) {
|
||||
j = (int)((mark->frequency - center_frequency) / frequency_range * (double) fft_size + width / 2 + 0.5);
|
||||
if (j < 0 || j >= width) /* check out-of-range, should not happen */
|
||||
continue;
|
||||
for (k = 0; k < HEIGHT; k++) {
|
||||
/* skip yellow/white graph */
|
||||
if (screen_color[k][j] == 13 || screen_color[k][j] == 17)
|
||||
continue;
|
||||
screen[k][j] = ':';
|
||||
screen_color[k][j] = 12;
|
||||
}
|
||||
sprintf(print_channel, "Ch%s", mark->kanal);
|
||||
for (o = 0; o < (int)strlen(print_channel); o++) {
|
||||
s = j - strlen(print_channel) + o;
|
||||
if (s >= 0 && s < width) {
|
||||
screen[HEIGHT - 1][s] = print_channel[o];
|
||||
screen_color[HEIGHT - 1][s] = 7;
|
||||
}
|
||||
}
|
||||
if (fmod(mark->frequency, 1000.0))
|
||||
sprintf(print_frequency, "%.4f", mark->frequency / 1e6);
|
||||
else
|
||||
sprintf(print_frequency, "%.3f", mark->frequency / 1e6);
|
||||
for (o = 0; o < (int)strlen(print_frequency); o++) {
|
||||
s = j + o + 1;
|
||||
if (s >= 0 && s < width) {
|
||||
screen[HEIGHT - 1][s] = print_frequency[o];
|
||||
screen_color[HEIGHT - 1][s] = 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* add center (DC line) to spectrum */
|
||||
j = width / 2 + 0.5;
|
||||
if (j < 1 || j >= width-1) /* check out-of-range, should not happen */
|
||||
continue;
|
||||
for (k = 0; k < HEIGHT; k++) {
|
||||
/* skip green/yellow/white graph */
|
||||
if (screen_color[k][j] == 13 || screen_color[k][j] == 17 || screen_color[k][j] == 12)
|
||||
continue;
|
||||
screen[k][j] = '.';
|
||||
screen_color[k][j] = 7;
|
||||
}
|
||||
screen[0][j-1] = 'D';
|
||||
screen[0][j+1] = 'C';
|
||||
screen_color[0][j-1] = 7;
|
||||
screen_color[0][j+1] = 7;
|
||||
/* display buffer */
|
||||
printf("\0337\033[H");
|
||||
for (j = 0; j < HEIGHT; j++) {
|
||||
for (k = 0; k < width; k++) {
|
||||
if (screen_color[j][k] != color) {
|
||||
color = screen_color[j][k];
|
||||
printf("\033[%d;3%dm", color / 10, color % 10);
|
||||
}
|
||||
putchar(screen[j][k]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
/* reset color and position */
|
||||
printf("\033[0;39m\0338"); fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
disp.interval_pos = pos;
|
||||
|
||||
unlock_debug();
|
||||
}
|
||||
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
/* display status functions
|
||||
*
|
||||
* (C) 2017 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 <pthread.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libdisplay/display.h"
|
||||
|
||||
static int status_on = 0;
|
||||
static int line_count = 0;
|
||||
static int lines_total = 0;
|
||||
static char screen[MAX_HEIGHT_STATUS][MAX_DISPLAY_WIDTH];
|
||||
|
||||
static void print_status(int on)
|
||||
{
|
||||
int i, j;
|
||||
int w, h;
|
||||
|
||||
get_win_size(&w, &h);
|
||||
if (w > MAX_DISPLAY_WIDTH - 1)
|
||||
w = MAX_DISPLAY_WIDTH - 1;
|
||||
|
||||
if (w > MAX_DISPLAY_WIDTH)
|
||||
w = MAX_DISPLAY_WIDTH;
|
||||
h--;
|
||||
if (h > lines_total)
|
||||
h = lines_total;
|
||||
|
||||
lock_debug();
|
||||
printf("\0337\033[H\033[1;37m");
|
||||
for (i = 0; i < h; i++) {
|
||||
j = 0;
|
||||
if (on) {
|
||||
for (j = 0; j < w; j++)
|
||||
putchar(screen[i][j]);
|
||||
} else {
|
||||
for (j = 0; j < w; j++)
|
||||
putchar(' ');
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
printf("\0338"); fflush(stdout);
|
||||
unlock_debug();
|
||||
}
|
||||
|
||||
void display_status_on(int on)
|
||||
{
|
||||
if (status_on)
|
||||
print_status(0);
|
||||
|
||||
if (on < 0)
|
||||
status_on = 1 - status_on;
|
||||
else
|
||||
status_on = on;
|
||||
|
||||
if (status_on)
|
||||
print_status(1);
|
||||
|
||||
if (status_on)
|
||||
debug_limit_scroll = lines_total;
|
||||
else
|
||||
debug_limit_scroll = 0;
|
||||
}
|
||||
|
||||
/* start status display */
|
||||
void display_status_start(void)
|
||||
{
|
||||
memset(screen, ' ', sizeof(screen));
|
||||
memset(screen[0], '-', sizeof(screen[0]));
|
||||
memcpy(screen[0] + 4, "Channel Status", 14);
|
||||
line_count = 1;
|
||||
}
|
||||
|
||||
void display_status_channel(const char *kanal, const char *type, const char *state)
|
||||
{
|
||||
char line[MAX_DISPLAY_WIDTH];
|
||||
|
||||
/* add empty line after previous channel+subscriber */
|
||||
if (line_count > 1 && line_count < MAX_HEIGHT_STATUS)
|
||||
line_count++;
|
||||
|
||||
if (line_count == MAX_HEIGHT_STATUS)
|
||||
return;
|
||||
|
||||
if (type)
|
||||
snprintf(line, sizeof(line), "Channel: %s Type: %s State: %s", kanal, type, state);
|
||||
else
|
||||
snprintf(line, sizeof(line), "Channel: %s State: %s", kanal, state);
|
||||
line[sizeof(line) - 1] = '\0';
|
||||
memcpy(screen[line_count++], line, strlen(line));
|
||||
}
|
||||
|
||||
void display_status_subscriber(const char *number, const char *state)
|
||||
{
|
||||
char line[MAX_DISPLAY_WIDTH];
|
||||
|
||||
if (line_count == MAX_HEIGHT_STATUS)
|
||||
return;
|
||||
|
||||
if (state)
|
||||
snprintf(line, sizeof(line), " Subscriber: %s State: %s", number, state);
|
||||
else
|
||||
snprintf(line, sizeof(line), " Subscriber: %s", number);
|
||||
line[sizeof(line) - 1] = '\0';
|
||||
memcpy(screen[line_count++], line, strlen(line));
|
||||
}
|
||||
|
||||
void display_status_end(void)
|
||||
{
|
||||
if (line_count < MAX_HEIGHT_STATUS) {
|
||||
memset(screen[line_count], '-', sizeof(screen[line_count]));
|
||||
line_count++;
|
||||
}
|
||||
/* if last total lines exceed current line count, keep it, so removed lines are overwritten with spaces */
|
||||
if (line_count > lines_total)
|
||||
lines_total = line_count;
|
||||
if (status_on)
|
||||
print_status(1);
|
||||
/* set new total lines */
|
||||
lines_total = line_count;
|
||||
if (status_on)
|
||||
debug_limit_scroll = lines_total;
|
||||
}
|
||||
|
||||
|
|
@ -1,252 +0,0 @@
|
|||
/* display wave form 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 <string.h>
|
||||
#include <pthread.h>
|
||||
#include <math.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libdisplay/display.h"
|
||||
|
||||
#define HEIGHT 11
|
||||
|
||||
static int num_sender = 0;
|
||||
static char screen[HEIGHT][MAX_DISPLAY_WIDTH];
|
||||
static int wave_on = 0;
|
||||
|
||||
void display_wave_init(dispwav_t *disp, int samplerate, const char *kanal)
|
||||
{
|
||||
memset(disp, 0, sizeof(*disp));
|
||||
disp->offset = (num_sender++) * HEIGHT;
|
||||
disp->interval_max = (double)samplerate * DISPLAY_INTERVAL + 0.5;
|
||||
disp->kanal = kanal;
|
||||
}
|
||||
|
||||
void display_wave_on(int on)
|
||||
{
|
||||
int i, j;
|
||||
int w, h;
|
||||
|
||||
get_win_size(&w, &h);
|
||||
if (w > MAX_DISPLAY_WIDTH - 1)
|
||||
w = MAX_DISPLAY_WIDTH - 1;
|
||||
|
||||
if (wave_on) {
|
||||
memset(&screen, ' ', sizeof(screen));
|
||||
lock_debug();
|
||||
printf("\0337\033[H");
|
||||
for (i = 0; i < num_sender; i++) {
|
||||
for (j = 0; j < HEIGHT; j++) {
|
||||
screen[j][w] = '\0';
|
||||
puts(screen[j]);
|
||||
}
|
||||
}
|
||||
printf("\0338"); fflush(stdout);
|
||||
unlock_debug();
|
||||
}
|
||||
|
||||
if (on < 0)
|
||||
wave_on = 1 - wave_on;
|
||||
else
|
||||
wave_on = on;
|
||||
|
||||
if (wave_on)
|
||||
debug_limit_scroll = HEIGHT * num_sender;
|
||||
else
|
||||
debug_limit_scroll = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* draw wave form:
|
||||
*
|
||||
* theoretical example: HEIGHT = 3 allows 5 steps
|
||||
*
|
||||
* Line 0: '.
|
||||
* Line 1: '.
|
||||
* Line 2: '
|
||||
*
|
||||
* HEIGHT is odd, so the center line's char is ''' (otherwise '.')
|
||||
* (HEIGHT - 1) / 2 = 1, so the center line is drawn in line 1
|
||||
*
|
||||
* y is in range of 0..4, so these are 5 steps, where 2 is the
|
||||
* center line. this is calculated by (HEIGHT * 2 - 1)
|
||||
*/
|
||||
void display_wave(dispwav_t *disp, sample_t *samples, int length, double range)
|
||||
{
|
||||
int pos, max;
|
||||
sample_t *buffer;
|
||||
int i, j, k, s, e;
|
||||
double last, current, next;
|
||||
int color = 9; /* default color */
|
||||
int center_line;
|
||||
char center_char;
|
||||
int width, h;
|
||||
|
||||
if (!wave_on)
|
||||
return;
|
||||
|
||||
lock_debug();
|
||||
|
||||
get_win_size(&width, &h);
|
||||
if (width > MAX_DISPLAY_WIDTH - 1)
|
||||
width = MAX_DISPLAY_WIDTH - 1;
|
||||
|
||||
/* at what line we draw our zero-line and what character we use */
|
||||
center_line = (HEIGHT - 1) >> 1;
|
||||
center_char = (HEIGHT & 1) ? '\'' : '.';
|
||||
|
||||
pos = disp->interval_pos;
|
||||
max = disp->interval_max;
|
||||
buffer = disp->buffer;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
if (pos >= width + 2) {
|
||||
if (++pos == max)
|
||||
pos = 0;
|
||||
continue;
|
||||
}
|
||||
buffer[pos++] = samples[i];
|
||||
if (pos == width + 2) {
|
||||
memset(&screen, ' ', sizeof(screen));
|
||||
for (j = 0; j < width; j++) {
|
||||
/* Input value is scaled to range -1 .. 1 and then subtracted from 1,
|
||||
* so the result ranges from 0 .. 2.
|
||||
* HEIGHT-1 is multiplied with the range, so a HEIGHT of 3 would allow
|
||||
* 0..4 (5 steps) and a HEIGHT of 11 would allow 0..20 (21 steps).
|
||||
* We always use odd number of steps, so there will be a center between
|
||||
* values.
|
||||
*/
|
||||
last = (1.0 - buffer[j] / range) * (double)(HEIGHT - 1);
|
||||
current = (1.0 - buffer[j + 1] / range) * (double)(HEIGHT - 1);
|
||||
next = (1.0 - buffer[j + 2] / range) * (double)(HEIGHT - 1);
|
||||
/* calculate start and end for vertical line
|
||||
* if the current value is a peak (above or below last AND next point),
|
||||
* round this peak point to become one end of the vertical line.
|
||||
* the other end is rounded up or down, so the end of the line will
|
||||
* not overlap with the ends of the surrounding lines.
|
||||
*/
|
||||
if (last > current) {
|
||||
if (next > current) {
|
||||
/* current point is a peak up */
|
||||
s = round(current);
|
||||
/* use lowest neighbor point and end is half way */
|
||||
if (last > next)
|
||||
e = floor((last + current) / 2.0);
|
||||
else
|
||||
e = floor((next + current) / 2.0);
|
||||
/* end point must not be above start point */
|
||||
if (e < s)
|
||||
e = s;
|
||||
} else {
|
||||
/* current point is a transition upwards */
|
||||
s = ceil((next + current) / 2.0);
|
||||
e = floor((last + current) / 2.0);
|
||||
/* end point must not be above start point */
|
||||
if (e < s)
|
||||
s = e = round(current);
|
||||
}
|
||||
} else {
|
||||
if (next <= current) {
|
||||
/* current point is a peak down */
|
||||
e = round(current);
|
||||
/* use heighes neighbor point and start is half way */
|
||||
if (last <= next)
|
||||
s = ceil((last + current) / 2.0);
|
||||
else
|
||||
s = ceil((next + current) / 2.0);
|
||||
/* start point must not be below end point */
|
||||
if (s > e)
|
||||
s = e;
|
||||
} else {
|
||||
/* current point is a transition downwards */
|
||||
s = ceil((last + current) / 2.0);
|
||||
e = floor((next + current) / 2.0);
|
||||
/* start point must not be below end point */
|
||||
if (s > e)
|
||||
s = e = round(current);
|
||||
}
|
||||
}
|
||||
/* only draw line, if it is in range */
|
||||
if (e >= 0 && s < HEIGHT * 2 - 1) {
|
||||
/* clip */
|
||||
if (s < 0)
|
||||
s = 0;
|
||||
if (e >= HEIGHT * 2 - 1)
|
||||
e = HEIGHT * 2 - 1;
|
||||
/* plot start and end point */
|
||||
if ((s & 1))
|
||||
screen[s >> 1][j] = '.';
|
||||
else if (e != s)
|
||||
screen[s >> 1][j] = '|';
|
||||
if (!(e & 1))
|
||||
screen[e >> 1][j] = '\'';
|
||||
else if (e != s)
|
||||
screen[e >> 1][j] = '|';
|
||||
/* plot line between start and end point */
|
||||
for (k = (s >> 1) + 1; k < (e >> 1); k++)
|
||||
screen[k][j] = '|';
|
||||
}
|
||||
}
|
||||
sprintf(screen[0], "(chan %s", disp->kanal);
|
||||
*strchr(screen[0], '\0') = ')';
|
||||
printf("\0337\033[H");
|
||||
for (j = 0; j < disp->offset; j++)
|
||||
puts("");
|
||||
for (j = 0; j < HEIGHT; j++) {
|
||||
for (k = 0; k < width; k++) {
|
||||
if (j == center_line && screen[j][k] == ' ') {
|
||||
/* blue 0-line */
|
||||
if (color != 4) {
|
||||
color = 4;
|
||||
printf("\033[0;34m");
|
||||
}
|
||||
putchar(center_char);
|
||||
} else if (screen[j][k] == '\'' || screen[j][k] == '.' || screen[j][k] == '|') {
|
||||
/* green scope curve */
|
||||
if (color != 2) {
|
||||
color = 2;
|
||||
printf("\033[1;32m");
|
||||
}
|
||||
putchar(screen[j][k]);
|
||||
} else if (screen[j][k] != ' ') {
|
||||
/* white other characters */
|
||||
if (color != 7) {
|
||||
color = 7;
|
||||
printf("\033[1;37m");
|
||||
}
|
||||
putchar(screen[j][k]);
|
||||
} else
|
||||
putchar(screen[j][k]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
/* reset color and position */
|
||||
printf("\033[0;39m\0338"); fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
disp->interval_pos = pos;
|
||||
|
||||
unlock_debug();
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libdtmf.a
|
||||
|
||||
libdtmf_a_SOURCES = \
|
||||
dtmf_encode.c \
|
||||
dtmf_decode.c
|
|
@ -1,259 +0,0 @@
|
|||
/* DTMF coder
|
||||
*
|
||||
* (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 <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "dtmf_decode.h"
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#define level2db(level) (20 * log10(level))
|
||||
#define db2level(db) pow(10, (double)db / 20.0)
|
||||
|
||||
#define DTMF_LOW_1 697.0
|
||||
#define DTMF_LOW_2 770.0
|
||||
#define DTMF_LOW_3 852.0
|
||||
#define DTMF_LOW_4 941.0
|
||||
#define DTMF_HIGH_1 1209.0
|
||||
#define DTMF_HIGH_2 1336.0
|
||||
#define DTMF_HIGH_3 1477.0
|
||||
#define DTMF_HIGH_4 1633.0
|
||||
|
||||
static const char dtmf_digit[] = " 123A456B789C*0#D";
|
||||
|
||||
#ifdef DEBUG
|
||||
const char *_debug_amplitude(double level)
|
||||
{
|
||||
static char text[42];
|
||||
|
||||
strcpy(text, " : ");
|
||||
if (level > 1.0)
|
||||
level = 1.0;
|
||||
if (level < -1.0)
|
||||
level = -1.0;
|
||||
text[20 + (int)(level * 20)] = '*';
|
||||
|
||||
return text;
|
||||
}
|
||||
#endif
|
||||
|
||||
int dtmf_decode_init(dtmf_dec_t *dtmf, void *priv, void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas), int samplerate, double max_amplitude, double min_amplitude)
|
||||
{
|
||||
int rc;
|
||||
|
||||
memset(dtmf, 0, sizeof(*dtmf));
|
||||
dtmf->priv = priv;
|
||||
dtmf->recv_digit = recv_digit;
|
||||
dtmf->samplerate = samplerate;
|
||||
dtmf->freq_margin = 1.03; /* 1.8 .. 3.5 % */
|
||||
dtmf->max_amplitude = max_amplitude;
|
||||
dtmf->min_amplitude = min_amplitude;
|
||||
dtmf->forward_twist = db2level(4.0);
|
||||
dtmf->reverse_twist = db2level(8.0);
|
||||
dtmf->time_detect = (int)(0.025 * (double)samplerate);
|
||||
dtmf->time_meas = (int)(0.015 * (double)samplerate);
|
||||
dtmf->time_pause = (int)(0.010 * (double)samplerate);
|
||||
|
||||
/* init fm demodulator */
|
||||
rc = fm_demod_init(&dtmf->demod_low, (double)samplerate, (DTMF_LOW_1 + DTMF_LOW_4) / 2.0, DTMF_LOW_4 - DTMF_LOW_1);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
rc = fm_demod_init(&dtmf->demod_high, (double)samplerate, (DTMF_HIGH_1 + DTMF_HIGH_4) / 2.0, DTMF_HIGH_4 - DTMF_HIGH_1);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
|
||||
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
|
||||
iir_lowpass_init(&dtmf->freq_lp[0], 100.0, samplerate, 2);
|
||||
iir_lowpass_init(&dtmf->freq_lp[1], 100.0, samplerate, 2);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
dtmf_decode_exit(dtmf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void dtmf_decode_exit(dtmf_dec_t *dtmf)
|
||||
{
|
||||
fm_demod_exit(&dtmf->demod_low);
|
||||
fm_demod_exit(&dtmf->demod_high);
|
||||
}
|
||||
|
||||
void dtmf_decode_filter(dtmf_dec_t *dtmf, sample_t *samples, int length, sample_t *frequency_low, sample_t *frequency_high, sample_t *amplitude_low, sample_t *amplitude_high)
|
||||
{
|
||||
sample_t I_low[length], Q_low[length];
|
||||
sample_t I_high[length], Q_high[length];
|
||||
int i;
|
||||
|
||||
fm_demodulate_real(&dtmf->demod_low, frequency_low, length, samples, I_low, Q_low);
|
||||
fm_demodulate_real(&dtmf->demod_high, frequency_high, length, samples, I_high, Q_high);
|
||||
/* peak amplitude is the length of I/Q vector
|
||||
* since we filter out the unwanted modulation product, the vector is only half of length */
|
||||
for (i = 0; i < length; i++) {
|
||||
amplitude_low[i] = sqrt(I_low[i] * I_low[i] + Q_low[i] * Q_low[i]) * 2.0;
|
||||
amplitude_high[i] = sqrt(I_high[i] * I_high[i] + Q_high[i] * Q_high[i]) * 2.0;
|
||||
}
|
||||
iir_process(&dtmf->freq_lp[0], frequency_low, length);
|
||||
iir_process(&dtmf->freq_lp[1], frequency_high, length);
|
||||
}
|
||||
void dtmf_decode(dtmf_dec_t *dtmf, sample_t *samples, int length)
|
||||
{
|
||||
sample_t frequency_low[length], amplitude_low[length];
|
||||
sample_t frequency_high[length], amplitude_high[length];
|
||||
double margin, min_amplitude, max_amplitude, forward_twist, reverse_twist, f1, f2;
|
||||
int time_detect, time_meas, time_pause;
|
||||
int low = 0, high = 0;
|
||||
char detected, digit;
|
||||
int count;
|
||||
int amplitude_ok, twist_ok;
|
||||
int i;
|
||||
|
||||
margin = dtmf->freq_margin;
|
||||
min_amplitude = dtmf->min_amplitude;
|
||||
max_amplitude = dtmf->max_amplitude;
|
||||
forward_twist = dtmf->forward_twist;
|
||||
reverse_twist = dtmf->reverse_twist;
|
||||
time_detect = dtmf->time_detect;
|
||||
time_meas = dtmf->time_meas;
|
||||
time_pause = dtmf->time_pause;
|
||||
detected = dtmf->detected;
|
||||
count = dtmf->count;
|
||||
|
||||
/* FM/AM demod */
|
||||
dtmf_decode_filter(dtmf, samples, length, frequency_low, frequency_high, amplitude_low, amplitude_high);
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
#ifdef DEBUG
|
||||
// printf("%s %.5f\n", _debug_amplitude(samples[i]/2.0), samples[i]/2.0);
|
||||
#endif
|
||||
/* get frequency of low frequencies, correct amplitude drop at cutoff point */
|
||||
f1 = frequency_low[i] + (DTMF_LOW_1 + DTMF_LOW_4) / 2.0;
|
||||
if (f1 >= DTMF_LOW_1 / margin && f1 <= DTMF_LOW_1 * margin) {
|
||||
/* cutoff point */
|
||||
amplitude_low[i] /= 0.7071;
|
||||
low = 1;
|
||||
f1 -= DTMF_LOW_1;
|
||||
} else
|
||||
if (f1 >= DTMF_LOW_2 / margin && f1 <= DTMF_LOW_2 * margin) {
|
||||
amplitude_low[i] /= 1.0734;
|
||||
low = 2;
|
||||
f1 -= DTMF_LOW_2;
|
||||
} else
|
||||
if (f1 >= DTMF_LOW_3 / margin && f1 <= DTMF_LOW_3 * margin) {
|
||||
amplitude_low[i] /= 1.0389;
|
||||
low = 3;
|
||||
f1 -= DTMF_LOW_3;
|
||||
} else
|
||||
if (f1 >= DTMF_LOW_4 / margin && f1 <= DTMF_LOW_4 * margin) {
|
||||
/* cutoff point */
|
||||
amplitude_low[i] /= 0.7071;
|
||||
low = 4;
|
||||
f1 -= DTMF_LOW_4;
|
||||
} else
|
||||
low = 0;
|
||||
/* get frequency of high frequencies, correct amplitude drop at cutoff point */
|
||||
f2 = frequency_high[i] + (DTMF_HIGH_1 + DTMF_HIGH_4) / 2.0;
|
||||
if (f2 >= DTMF_HIGH_1 / margin && f2 <= DTMF_HIGH_1 * margin) {
|
||||
/* cutoff point */
|
||||
amplitude_high[i] /= 0.7071;
|
||||
high = 1;
|
||||
f2 -= DTMF_HIGH_1;
|
||||
} else
|
||||
if (f2 >= DTMF_HIGH_2 / margin && f2 <= DTMF_HIGH_2 * margin) {
|
||||
amplitude_high[i] /= 1.0731;
|
||||
high = 2;
|
||||
f2 -= DTMF_HIGH_2;
|
||||
} else
|
||||
if (f2 >= DTMF_HIGH_3 / margin && f2 <= DTMF_HIGH_3 * margin) {
|
||||
amplitude_high[i] /= 1.0372;
|
||||
high = 3;
|
||||
f2 -= DTMF_HIGH_3;
|
||||
} else
|
||||
if (f2 >= DTMF_HIGH_4 / margin && f2 <= DTMF_HIGH_4 * margin) {
|
||||
/* cutoff point */
|
||||
amplitude_high[i] /= 0.7071;
|
||||
high = 4;
|
||||
f2 -= DTMF_HIGH_4;
|
||||
} else
|
||||
high = 0;
|
||||
digit = 0;
|
||||
amplitude_ok = 0;
|
||||
twist_ok = 0;
|
||||
if (low && high) {
|
||||
digit = dtmf_digit[low*4+high];
|
||||
/* check for limits */
|
||||
if (amplitude_low[i] <= max_amplitude && amplitude_low[i] >= min_amplitude && amplitude_high[i] <= max_amplitude && amplitude_high[i] >= min_amplitude) {
|
||||
amplitude_ok = 1;
|
||||
#ifdef DEBUG
|
||||
printf("%.1f %.1f (limits %.1f .. %.1f) %.1f\n", level2db(amplitude_low[i]), level2db(amplitude_high[i]), level2db(min_amplitude), level2db(max_amplitude), level2db(amplitude_high[i] / amplitude_low[i]));
|
||||
#endif
|
||||
if (amplitude_high[i] / amplitude_low[i] <= forward_twist && amplitude_low[i] / amplitude_high[i] <= reverse_twist)
|
||||
twist_ok = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!detected) {
|
||||
if (digit && amplitude_ok && twist_ok) {
|
||||
if (count == 0) {
|
||||
memset(&dtmf->meas, 0, sizeof(dtmf->meas));
|
||||
}
|
||||
if (count >= time_meas) {
|
||||
dtmf->meas.frequency_low += f1;
|
||||
dtmf->meas.frequency_high += f2;
|
||||
dtmf->meas.amplitude_low += amplitude_low[i];
|
||||
dtmf->meas.amplitude_high += amplitude_high[i];
|
||||
dtmf->meas.count++;
|
||||
}
|
||||
count++;
|
||||
if (count >= time_detect) {
|
||||
detected = digit;
|
||||
dtmf->meas.frequency_low /= dtmf->meas.count;
|
||||
dtmf->meas.frequency_high /= dtmf->meas.count;
|
||||
dtmf->meas.amplitude_low /= dtmf->meas.count;
|
||||
dtmf->meas.amplitude_high /= dtmf->meas.count;
|
||||
dtmf->meas.count = 1;
|
||||
dtmf->recv_digit(dtmf->priv, digit, &dtmf->meas);
|
||||
}
|
||||
} else
|
||||
count = 0;
|
||||
} else {
|
||||
if (!digit || digit != detected || !amplitude_ok || !twist_ok) {
|
||||
count++;
|
||||
if (count >= time_pause) {
|
||||
detected = 0;
|
||||
#ifdef DEBUG
|
||||
printf("lost!\n");
|
||||
#endif
|
||||
}
|
||||
} else
|
||||
count = 0;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
if (digit)
|
||||
printf("DTMF tone='%c' diff frequency=%.1f %.1f amplitude=%.1f %.1f dB (%s) twist=%.1f dB (%s)\n", digit, f1, f2, level2db(amplitude_low[i]), level2db(amplitude_high[i]), (amplitude_ok) ? "OK" : "nok", level2db(amplitude_high[i] / amplitude_low[i]), (twist_ok) ? "OK" : "nok");
|
||||
#endif
|
||||
|
||||
dtmf->detected = detected;
|
||||
dtmf->count = count;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
#include "../libfm/fm.h"
|
||||
|
||||
typedef struct ftmf_meas {
|
||||
double frequency_low;
|
||||
double frequency_high;
|
||||
double amplitude_low;
|
||||
double amplitude_high;
|
||||
int count;
|
||||
} dtmf_meas_t;
|
||||
|
||||
typedef struct dtmf_dec {
|
||||
void *priv;
|
||||
void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas);
|
||||
int samplerate; /* samplerate */
|
||||
double freq_margin; /* +- limit of frequency deviation (percent) valid tone*/
|
||||
double min_amplitude; /* minimum amplitude relative to 0 dBm */
|
||||
double max_amplitude; /* maximum amplitude relative to 0 dBm */
|
||||
double forward_twist; /* how much do higher frequencies are louder than lower frequencies */
|
||||
double reverse_twist; /* how much do lower frequencies are louder than higher frequencies */
|
||||
int time_detect;
|
||||
int time_meas;
|
||||
int time_pause;
|
||||
fm_demod_t demod_low; /* demodulator for low frequencies */
|
||||
fm_demod_t demod_high; /* demodulator for high frequencies */
|
||||
iir_filter_t freq_lp[2]; /* low pass to filter the frequency result */
|
||||
char detected; /* currently detected DTMF digit or 0 for no detection */
|
||||
int count; /* counter to count detection or loss (pause) of signal */
|
||||
dtmf_meas_t meas; /* measurements */
|
||||
} dtmf_dec_t;
|
||||
|
||||
int dtmf_decode_init(dtmf_dec_t *dtmf, void *priv, void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas), int samplerate, double max_amplitude, double min_amplitude);
|
||||
void dtmf_decode_exit(dtmf_dec_t *dtmf);
|
||||
void dtmf_decode(dtmf_dec_t *dtmf, sample_t *samples, int length);
|
||||
void dtmf_decode_filter(dtmf_dec_t *dtmf, sample_t *samples, int length, sample_t *frequency_low, sample_t *frequency_high, sample_t *amplitude_low, sample_t *amplitude_high);
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
/* DTMF coder
|
||||
*
|
||||
* (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 <string.h>
|
||||
#include <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "dtmf_encode.h"
|
||||
|
||||
#define PI M_PI
|
||||
|
||||
#define PEAK_DTMF_LOW 0.2818 /* -11 dBm, relative to 0 dBm level */
|
||||
#define PEAK_DTMF_HIGH 0.3548 /* -9 dBm, relative to 0 dBm level */
|
||||
#define DTMF_DURATION 0.100 /* duration in seconds */
|
||||
|
||||
static sample_t dsp_sine_dtmf_low[65536];
|
||||
static sample_t dsp_sine_dtmf_high[65536];
|
||||
|
||||
void dtmf_encode_init(dtmf_enc_t *dtmf, int samplerate, double dBm_level)
|
||||
{
|
||||
int i;
|
||||
|
||||
memset(dtmf, 0, sizeof(*dtmf));
|
||||
dtmf->samplerate = samplerate;
|
||||
dtmf->max = (int)((double)samplerate * DTMF_DURATION + 0.5);
|
||||
|
||||
// FIXME: do this globally and not per instance */
|
||||
for (i = 0; i < 65536; i++) {
|
||||
dsp_sine_dtmf_low[i] = sin((double)i / 65536.0 * 2.0 * PI) * PEAK_DTMF_LOW * dBm_level;
|
||||
dsp_sine_dtmf_high[i] = sin((double)i / 65536.0 * 2.0 * PI) * PEAK_DTMF_HIGH * dBm_level;
|
||||
}
|
||||
}
|
||||
|
||||
/* set dtmf tone */
|
||||
void dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone)
|
||||
{
|
||||
double f1, f2;
|
||||
|
||||
switch(tone) {
|
||||
case '1': f1 = 697.0; f2 = 1209.0; break;
|
||||
case '2': f1 = 697.0; f2 = 1336.0; break;
|
||||
case '3': f1 = 697.0; f2 = 1477.0; break;
|
||||
case'a':case 'A': f1 = 697.0; f2 = 1633.0; break;
|
||||
case '4': f1 = 770.0; f2 = 1209.0; break;
|
||||
case '5': f1 = 770.0; f2 = 1336.0; break;
|
||||
case '6': f1 = 770.0; f2 = 1477.0; break;
|
||||
case'b':case 'B': f1 = 770.0; f2 = 1633.0; break;
|
||||
case '7': f1 = 852.0; f2 = 1209.0; break;
|
||||
case '8': f1 = 852.0; f2 = 1336.0; break;
|
||||
case '9': f1 = 852.0; f2 = 1477.0; break;
|
||||
case'c':case 'C': f1 = 852.0; f2 = 1633.0; break;
|
||||
case '*': f1 = 941.0; f2 = 1209.0; break;
|
||||
case '0': f1 = 941.0; f2 = 1336.0; break;
|
||||
case '#': f1 = 941.0; f2 = 1477.0; break;
|
||||
case'd':case 'D': f1 = 941.0; f2 = 1633.0; break;
|
||||
default:
|
||||
dtmf->tone = 0;
|
||||
return;
|
||||
}
|
||||
dtmf->tone = tone;
|
||||
dtmf->pos = 0;
|
||||
dtmf->phaseshift65536[0] = 65536.0 / ((double)dtmf->samplerate / f1);
|
||||
dtmf->phaseshift65536[1] = 65536.0 / ((double)dtmf->samplerate / f2);
|
||||
}
|
||||
|
||||
/* Generate audio stream from DTMF tone. Keep phase for next call of function. */
|
||||
void dtmf_encode(dtmf_enc_t *dtmf, sample_t *samples, int length)
|
||||
{
|
||||
double *phaseshift, *phase;
|
||||
int i, pos, max;
|
||||
|
||||
/* use silence, if no tone */
|
||||
if (!dtmf->tone) {
|
||||
memset(samples, 0, length * sizeof(*samples));
|
||||
return;
|
||||
}
|
||||
|
||||
phaseshift = dtmf->phaseshift65536;
|
||||
phase = dtmf->phase65536;
|
||||
pos = dtmf->pos;
|
||||
max = dtmf->max;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
*samples++ = dsp_sine_dtmf_low[(uint16_t)phase[0]]
|
||||
+ dsp_sine_dtmf_high[(uint16_t)phase[1]];
|
||||
phase[0] += phaseshift[0];
|
||||
if (phase[0] >= 65536)
|
||||
phase[0] -= 65536;
|
||||
phase[1] += phaseshift[1];
|
||||
if (phase[1] >= 65536)
|
||||
phase[1] -= 65536;
|
||||
|
||||
/* tone ends */
|
||||
if (++pos == max) {
|
||||
dtmf->tone = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
length -= i;
|
||||
|
||||
dtmf->pos = pos;
|
||||
|
||||
/* if tone ends, fill rest with silence */
|
||||
if (length)
|
||||
memset(samples, 0, length * sizeof(*samples));
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
|
||||
typedef struct dtmf_enc {
|
||||
int samplerate; /* samplerate */
|
||||
char tone; /* current tone to be played */
|
||||
int pos; /* sample counter for tone */
|
||||
int max; /* max number of samples for tone duration */
|
||||
double phaseshift65536[2]; /* how much the phase of sine wave changes per sample */
|
||||
double phase65536[2]; /* current phase */
|
||||
} dtmf_enc_t;
|
||||
|
||||
void dtmf_encode_init(dtmf_enc_t *dtmf, int samplerate, double dBm_level);
|
||||
void dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone);
|
||||
void dtmf_encode(dtmf_enc_t *dtmf, sample_t *samples, int length);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libemphasis.a
|
||||
|
||||
libemphasis_a_SOURCES = \
|
||||
emphasis.c
|
|
@ -1,144 +0,0 @@
|
|||
/* Pre-Emphasis and De-Emphasis implementation
|
||||
*
|
||||
* (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 <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libfilter/iir_filter.h"
|
||||
#include "emphasis.h"
|
||||
|
||||
#define PI M_PI
|
||||
|
||||
static void gen_sine(sample_t *samples, int num, int samplerate, double freq)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num; i++)
|
||||
samples[i] = cos(2.0 * M_PI * freq / (double)samplerate * (double)i);
|
||||
}
|
||||
|
||||
static double get_level(sample_t *samples, int num)
|
||||
{
|
||||
int i;
|
||||
double envelope = 0;
|
||||
for (i = num/2; i < num; i++) {
|
||||
if (samples[i] > envelope)
|
||||
envelope = samples[i];
|
||||
}
|
||||
|
||||
return envelope;
|
||||
}
|
||||
|
||||
/* calculate cut off from time constant in uS */
|
||||
double timeconstant2cutoff(double time_constant_us)
|
||||
{
|
||||
return 1.0 / (2.0 * PI * time_constant_us / 1e6);
|
||||
}
|
||||
|
||||
int init_emphasis(emphasis_t *state, int samplerate, double cut_off, double cut_off_h, double cut_off_l)
|
||||
{
|
||||
double factor;
|
||||
sample_t test_samples[samplerate / 10];
|
||||
|
||||
memset(state, 0, sizeof(*state));
|
||||
|
||||
/* exp (-2 * PI * CUT_OFF * delta_t) */
|
||||
factor = exp(-2.0 * PI * cut_off / (double)samplerate); /* 1/samplerate == delta_t */
|
||||
|
||||
// printf("Emphasis factor = %.3f\n", factor);
|
||||
state->p.factor = factor;
|
||||
state->p.amp = 1.0;
|
||||
state->d.factor = factor;
|
||||
state->d.amp = 1.0;
|
||||
|
||||
/* do not de-emphasis below CUT_OFF_H */
|
||||
iir_highpass_init(&state->d.hp, cut_off_h, samplerate, 1);
|
||||
|
||||
/* do not pre-emphasis above CUT_OFF_L
|
||||
* Mobile network specifications want -18 dB per octave.
|
||||
* With two iterations we have 24 dB, - 6 dB (from emphasis). */
|
||||
iir_lowpass_init(&state->p.lp, cut_off_l, samplerate, 2);
|
||||
|
||||
/* calibrate amplification to be neutral at 1000 Hz */
|
||||
gen_sine(test_samples, sizeof(test_samples) / sizeof(test_samples[0]), samplerate, 1000.0);
|
||||
pre_emphasis(state, test_samples, sizeof(test_samples) / sizeof(test_samples[0]));
|
||||
state->p.amp = 1.0 / get_level(test_samples, sizeof(test_samples) / sizeof(test_samples[0]));
|
||||
gen_sine(test_samples, sizeof(test_samples) / sizeof(test_samples[0]), samplerate, 1000.0);
|
||||
de_emphasis(state, test_samples, sizeof(test_samples) / sizeof(test_samples[0]));
|
||||
state->d.amp = 1.0 / get_level(test_samples, sizeof(test_samples) / sizeof(test_samples[0]));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pre_emphasis(emphasis_t *state, sample_t *samples, int num)
|
||||
{
|
||||
double x, y, x_last, factor, amp;
|
||||
int i;
|
||||
|
||||
iir_process(&state->p.lp, samples, num);
|
||||
|
||||
x_last = state->p.x_last;
|
||||
factor = state->p.factor;
|
||||
amp = state->p.amp;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
x = *samples;
|
||||
|
||||
/* pre-emphasis */
|
||||
y = x - factor * x_last;
|
||||
|
||||
x_last = x;
|
||||
|
||||
*samples++ = amp * y;
|
||||
}
|
||||
|
||||
state->p.x_last = x_last;
|
||||
}
|
||||
|
||||
void de_emphasis(emphasis_t *state, sample_t *samples, int num)
|
||||
{
|
||||
double x, y, y_last, factor, amp;
|
||||
int i;
|
||||
|
||||
y_last = state->d.y_last;
|
||||
factor = state->d.factor;
|
||||
amp = state->d.amp;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
x = *samples;
|
||||
|
||||
/* de-emphasis */
|
||||
y = x + factor * y_last;
|
||||
|
||||
y_last = y;
|
||||
|
||||
*samples++ = amp * y;
|
||||
}
|
||||
|
||||
state->d.y_last = y_last;
|
||||
}
|
||||
|
||||
/* high pass filter to remove DC and low frequencies */
|
||||
void dc_filter(emphasis_t *state, sample_t *samples, int num)
|
||||
{
|
||||
iir_process(&state->d.hp, samples, num);
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#include "../libfilter/iir_filter.h"
|
||||
|
||||
typedef struct emphasis {
|
||||
struct {
|
||||
iir_filter_t lp;
|
||||
double x_last;
|
||||
double factor;
|
||||
double amp;
|
||||
} p;
|
||||
struct {
|
||||
iir_filter_t hp;
|
||||
double y_last;
|
||||
double factor;
|
||||
double amp;
|
||||
} d;
|
||||
} emphasis_t;
|
||||
|
||||
/* refers to NMT specs, cnetz uses different emphasis cutoff */
|
||||
#define CUT_OFF_EMPHASIS_DEFAULT 300.0
|
||||
#define CUT_OFF_HIGHPASS_DEFAULT 300.0
|
||||
#define CUT_OFF_LOWPASS_DEFAULT 3400.0
|
||||
|
||||
double timeconstant2cutoff(double time_constant_us);
|
||||
int init_emphasis(emphasis_t *state, int samplerate, double cut_off, double cut_off_h, double cut_off_l);
|
||||
void pre_emphasis(emphasis_t *state, sample_t *samples, int num);
|
||||
void de_emphasis(emphasis_t *state, sample_t *samples, int num);
|
||||
void dc_filter(emphasis_t *state, sample_t *samples, int num);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libfft.a
|
||||
|
||||
libfft_a_SOURCES = \
|
||||
fft.c
|
|
@ -1,96 +0,0 @@
|
|||
/* Fast Fourier Transformation (FFT)
|
||||
*
|
||||
* (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 <math.h>
|
||||
#include <string.h>
|
||||
#include "fft.h"
|
||||
|
||||
/*
|
||||
* Code based closely to work by Paul Bourke
|
||||
*
|
||||
* This computes an in-place complex-to-complex FFT
|
||||
* x and y are the real and imaginary arrays of 2^m points.
|
||||
* dir = 1 gives forward transform
|
||||
* dir = -1 gives reverse transform
|
||||
*/
|
||||
void fft_process(int dir, int m, double *x, double *y)
|
||||
{
|
||||
int n, i, i1, j, k, i2, l, l1, l2;
|
||||
double c1, c2, tx, ty, t1, t2, u1, u2, z;
|
||||
|
||||
/* Calculate the number of points */
|
||||
n = 1 << m;
|
||||
|
||||
/* Do the bit reversal */
|
||||
i2 = n >> 1;
|
||||
j = 0;
|
||||
for (i = 0; i < n - 1; i++) {
|
||||
if (i < j) {
|
||||
tx = x[i];
|
||||
ty = y[i];
|
||||
x[i] = x[j];
|
||||
y[i] = y[j];
|
||||
x[j] = tx;
|
||||
y[j] = ty;
|
||||
}
|
||||
k = i2;
|
||||
while (k <= j) {
|
||||
j -= k;
|
||||
k >>= 1;
|
||||
}
|
||||
j += k;
|
||||
}
|
||||
|
||||
/* Compute the FFT */
|
||||
c1 = -1.0;
|
||||
c2 = 0.0;
|
||||
l2 = 1;
|
||||
for (l = 0; l < m; l++) {
|
||||
l1 = l2;
|
||||
l2 <<= 1;
|
||||
u1 = 1.0;
|
||||
u2 = 0.0;
|
||||
for (j = 0; j < l1; j++) {
|
||||
for (i = j; i < n; i += l2) {
|
||||
i1 = i + l1;
|
||||
t1 = u1 * x[i1] - u2 * y[i1];
|
||||
t2 = u1 * y[i1] + u2 * x[i1];
|
||||
x[i1] = x[i] - t1;
|
||||
y[i1] = y[i] - t2;
|
||||
x[i] += t1;
|
||||
y[i] += t2;
|
||||
}
|
||||
z = u1 * c1 - u2 * c2;
|
||||
u2 = u1 * c2 + u2 * c1;
|
||||
u1 = z;
|
||||
}
|
||||
c2 = sqrt((1.0 - c1) / 2.0);
|
||||
if (dir == 1)
|
||||
c2 = -c2;
|
||||
c1 = sqrt((1.0 + c1) / 2.0);
|
||||
}
|
||||
|
||||
/* Scaling for forward transform */
|
||||
if (dir == 1) {
|
||||
for (i = 0; i < n; i++) {
|
||||
x[i] /= n;
|
||||
y[i] /= n;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
|
||||
void fft_process(int dir, int m, double *x, double *y);
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libfilter.a
|
||||
|
||||
libfilter_a_SOURCES = \
|
||||
iir_filter.c \
|
||||
fir_filter.c
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
/* FIR filter
|
||||
*
|
||||
* (C) 2017 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 "../libsample/sample.h"
|
||||
#include "fir_filter.h"
|
||||
|
||||
//#define DEBUG_TAPS
|
||||
|
||||
static void kernel(double *taps, int M, double cutoff, int invert)
|
||||
{
|
||||
int i;
|
||||
double sum;
|
||||
|
||||
for (i = 0; i <= M; i++) {
|
||||
/* gen sinc */
|
||||
if (i == M / 2)
|
||||
taps[i] = 2.0 * M_PI * cutoff;
|
||||
else
|
||||
taps[i] = sin(2.0 * M_PI * cutoff * (double)(i - M / 2))
|
||||
/ (double)(i - M / 2);
|
||||
/* blackman window */
|
||||
taps[i] *= 0.42 - 0.50 * cos(2 * M_PI * (double)(i / M))
|
||||
+ 0.08 * cos(4 * M_PI * (double)(i / M));
|
||||
}
|
||||
|
||||
/* normalize */
|
||||
sum = 0;
|
||||
for (i = 0; i <= M; i++)
|
||||
sum += taps[i];
|
||||
for (i = 0; i <= M; i++)
|
||||
taps[i] /= sum;
|
||||
|
||||
/* invert */
|
||||
if (invert) {
|
||||
for (i = 0; i <= M; i++)
|
||||
taps[i] = -taps[i];
|
||||
taps[M / 2] += 1.0;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_TAPS
|
||||
puts("start");
|
||||
for (i = 0; i <= M; i++)
|
||||
puts(debug_amplitude(taps[i]));
|
||||
#endif
|
||||
}
|
||||
|
||||
static fir_filter_t *fir_init(double samplerate, double transition_bandwidth)
|
||||
{
|
||||
fir_filter_t *fir;
|
||||
int M;
|
||||
|
||||
/* alloc struct */
|
||||
fir = calloc(1, sizeof(*fir));
|
||||
if (!fir) {
|
||||
fprintf(stderr, "No memory creating FIR filter!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* transition bandwidth */
|
||||
M = ceil(1.0 / (transition_bandwidth / samplerate));
|
||||
if ((M & 1))
|
||||
M++;
|
||||
|
||||
// printf("cutoff=%.4f\n", cutoff / samplerate);
|
||||
// printf("tb=%.4f\n", transition_bandwidth / samplerate);
|
||||
fir->ntaps = M + 1;
|
||||
fir->delay = M / 2;
|
||||
|
||||
/* alloc taps */
|
||||
fir->taps = calloc(fir->ntaps, sizeof(*fir->taps));
|
||||
if (!fir->taps) {
|
||||
fprintf(stderr, "No memory creating FIR filter!\n");
|
||||
fir_exit(fir);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* alloc ring buffer */
|
||||
fir->buffer = calloc(fir->ntaps, sizeof(*fir->buffer));
|
||||
if (!fir->buffer) {
|
||||
fprintf(stderr, "No memory creating FIR filter!\n");
|
||||
fir_exit(fir);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
return fir;
|
||||
}
|
||||
|
||||
fir_filter_t *fir_lowpass_init(double samplerate, double cutoff, double transition_bandwidth)
|
||||
{
|
||||
/* calculate kernel */
|
||||
fir_filter_t *fir = fir_init(samplerate, transition_bandwidth);
|
||||
if (!fir)
|
||||
return NULL;
|
||||
kernel(fir->taps, fir->ntaps - 1, cutoff / samplerate, 0);
|
||||
return fir;
|
||||
}
|
||||
|
||||
fir_filter_t *fir_highpass_init(double samplerate, double cutoff, double transition_bandwidth)
|
||||
{
|
||||
fir_filter_t *fir = fir_init(samplerate, transition_bandwidth);
|
||||
if (!fir)
|
||||
return NULL;
|
||||
kernel(fir->taps, fir->ntaps - 1, cutoff / samplerate, 1);
|
||||
return fir;
|
||||
}
|
||||
|
||||
fir_filter_t *fir_allpass_init(double samplerate, double transition_bandwidth)
|
||||
{
|
||||
fir_filter_t *fir = fir_init(samplerate, transition_bandwidth);
|
||||
if (!fir)
|
||||
return NULL;
|
||||
fir->taps[(fir->ntaps - 1) / 2] = 1.0;
|
||||
return fir;
|
||||
}
|
||||
|
||||
fir_filter_t *fir_twopass_init(double samplerate, double cutoff_low, double cutoff_high, double transition_bandwidth)
|
||||
{
|
||||
int i;
|
||||
double sum;
|
||||
fir_filter_t *fir = fir_init(samplerate, transition_bandwidth);
|
||||
if (!fir)
|
||||
return NULL;
|
||||
double lp_taps[fir->ntaps], hp_taps[fir->ntaps];
|
||||
kernel(lp_taps, fir->ntaps - 1, cutoff_low / samplerate, 0);
|
||||
kernel(hp_taps, fir->ntaps - 1, cutoff_high / samplerate, 1);
|
||||
sum = 0;
|
||||
printf("#warning does not work as expected\n");
|
||||
abort();
|
||||
for (i = 0; i < fir->ntaps; i++) {
|
||||
fir->taps[i] = lp_taps[i] + hp_taps[i];
|
||||
sum += fir->taps[i];
|
||||
}
|
||||
/* hp will die */
|
||||
// for (i = 0; i < fir->ntaps; i++)
|
||||
// fir->taps[i] /= sum;
|
||||
return fir;
|
||||
}
|
||||
|
||||
void fir_exit(fir_filter_t *fir)
|
||||
{
|
||||
if (!fir)
|
||||
return;
|
||||
free(fir->taps);
|
||||
free(fir->buffer);
|
||||
free(fir);
|
||||
}
|
||||
|
||||
void fir_process(fir_filter_t *fir, sample_t *samples, int num)
|
||||
{
|
||||
int i, j;
|
||||
double y;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
/* put sample in ring buffer */
|
||||
fir->buffer[fir->buffer_pos] = samples[i];
|
||||
if (++fir->buffer_pos == fir->ntaps)
|
||||
fir->buffer_pos = 0;
|
||||
|
||||
/* convolve samples */
|
||||
y = 0;
|
||||
for (j = 0; j < fir->ntaps; j++) {
|
||||
/* convolve sample from ring buffer, starting with oldest */
|
||||
y += fir->buffer[fir->buffer_pos] * fir->taps[j];
|
||||
if (++fir->buffer_pos == fir->ntaps)
|
||||
fir->buffer_pos = 0;
|
||||
}
|
||||
samples[i] = y;
|
||||
}
|
||||
}
|
||||
|
||||
int fir_get_delay(fir_filter_t *fir)
|
||||
{
|
||||
return fir->delay;
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
#ifndef _FIR_FILTER_H
|
||||
#define _FIR_FILTER_H
|
||||
|
||||
typedef struct fir_filter {
|
||||
int ntaps;
|
||||
int delay;
|
||||
double *taps;
|
||||
double *buffer;
|
||||
int buffer_pos;
|
||||
} fir_filter_t;
|
||||
|
||||
fir_filter_t *fir_lowpass_init(double samplerate, double cutoff, double transition_bandwidth);
|
||||
fir_filter_t *fir_highpass_init(double samplerate, double cutoff, double transition_bandwidth);
|
||||
fir_filter_t *fir_allpass_init(double samplerate, double transition_bandwidth);
|
||||
fir_filter_t *fir_twopass_init(double samplerate, double cutoff_low, double cutoff_high, double transition_bandwidth);
|
||||
void fir_exit(fir_filter_t *fir);
|
||||
void fir_process(fir_filter_t *fir, sample_t *samples, int num);
|
||||
int fir_get_delay(fir_filter_t *fir);
|
||||
|
||||
#endif /* _FIR_FILTER_H */
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
/* 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 "../libsample/sample.h"
|
||||
#include "iir_filter.h"
|
||||
|
||||
//#define DEBUG_NAN
|
||||
|
||||
#define PI M_PI
|
||||
|
||||
void iir_lowpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations)
|
||||
{
|
||||
double Fc, Q, K, norm;
|
||||
|
||||
if (iterations > 64) {
|
||||
fprintf(stderr, "%s failed: too many iterations, please fix!\n", __func__);
|
||||
abort();
|
||||
}
|
||||
|
||||
memset(filter, 0, sizeof(*filter));
|
||||
filter->iter = iterations;
|
||||
Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */
|
||||
Fc = frequency / (double)samplerate;
|
||||
K = tan(PI * Fc);
|
||||
norm = 1 / (1 + K / Q + K * K);
|
||||
filter->a0 = K * K * norm;
|
||||
filter->a1 = 2 * filter->a0;
|
||||
filter->a2 = filter->a0;
|
||||
filter->b1 = 2 * (K * K - 1) * norm;
|
||||
filter->b2 = (1 - K / Q + K * K) * norm;
|
||||
#ifdef DEBUG_NAN
|
||||
printf("%p\n", filter);
|
||||
#endif
|
||||
}
|
||||
|
||||
void iir_highpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations)
|
||||
{
|
||||
double Fc, Q, K, norm;
|
||||
|
||||
memset(filter, 0, sizeof(*filter));
|
||||
filter->iter = iterations;
|
||||
Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */
|
||||
Fc = frequency / (double)samplerate;
|
||||
K = tan(PI * Fc);
|
||||
norm = 1 / (1 + K / Q + K * K);
|
||||
filter->a0 = 1 * norm;
|
||||
filter->a1 = -2 * filter->a0;
|
||||
filter->a2 = filter->a0;
|
||||
filter->b1 = 2 * (K * K - 1) * norm;
|
||||
filter->b2 = (1 - K / Q + K * K) * norm;
|
||||
}
|
||||
|
||||
void iir_bandpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations)
|
||||
{
|
||||
double Fc, Q, K, norm;
|
||||
|
||||
memset(filter, 0, sizeof(*filter));
|
||||
filter->iter = iterations;
|
||||
Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */
|
||||
Fc = frequency / (double)samplerate;
|
||||
K = tan(PI * Fc);
|
||||
norm = 1 / (1 + K / Q + K * K);
|
||||
filter->a0 = K / Q * norm;
|
||||
filter->a1 = 0;
|
||||
filter->a2 = -filter->a0;
|
||||
filter->b1 = 2 * (K * K - 1) * norm;
|
||||
filter->b2 = (1 - K / Q + K * K) * norm;
|
||||
}
|
||||
|
||||
void iir_notch_init(iir_filter_t *filter, double frequency, int samplerate, int iterations)
|
||||
{
|
||||
double Fc, Q, K, norm;
|
||||
|
||||
memset(filter, 0, sizeof(*filter));
|
||||
filter->iter = iterations;
|
||||
Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */
|
||||
Fc = frequency / (double)samplerate;
|
||||
K = tan(PI * Fc);
|
||||
norm = 1 / (1 + K / Q + K * K);
|
||||
filter->a0 = (1 + K * K) * norm;
|
||||
filter->a1 = 2 * (K * K - 1) * norm;
|
||||
filter->a2 = filter->a0;
|
||||
filter->b1 = filter->a1;
|
||||
filter->b2 = (1 - K / Q + K * K) * norm;
|
||||
}
|
||||
|
||||
void iir_process(iir_filter_t *filter, sample_t *samples, int length)
|
||||
{
|
||||
double a0, a1, a2, b1, b2;
|
||||
double *z1, *z2;
|
||||
double in, out;
|
||||
int iterations = filter->iter;
|
||||
int i, j;
|
||||
|
||||
/* get states */
|
||||
a0 = filter->a0;
|
||||
a1 = filter->a1;
|
||||
a2 = filter->a2;
|
||||
b1 = filter->b1;
|
||||
b2 = filter->b2;
|
||||
|
||||
/* these are state pointers, so no need to write back */
|
||||
z1 = filter->z1;
|
||||
z2 = filter->z2;
|
||||
|
||||
/* process filter */
|
||||
for (i = 0; i < length; i++) {
|
||||
/* add a small value, otherwise this loop will perform really bad on my 'nuedel' machine!!! */
|
||||
in = *samples + 0.000000001;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_NAN
|
||||
#pragma GCC push_options
|
||||
//#pragma GCC optimize ("O0")
|
||||
#endif
|
||||
|
||||
void iir_process_baseband(iir_filter_t *filter, float *baseband, int length)
|
||||
{
|
||||
double a0, a1, a2, b1, b2;
|
||||
double *z1, *z2;
|
||||
double in, out;
|
||||
int iterations = filter->iter;
|
||||
int i, j;
|
||||
|
||||
/* get states */
|
||||
a0 = filter->a0;
|
||||
a1 = filter->a1;
|
||||
a2 = filter->a2;
|
||||
b1 = filter->b1;
|
||||
b2 = filter->b2;
|
||||
|
||||
/* these are state pointers, so no need to write back */
|
||||
z1 = filter->z1;
|
||||
z2 = filter->z2;
|
||||
|
||||
/* process filter */
|
||||
for (i = 0; i < length; i++) {
|
||||
/* add a small value, otherwise this loop will perform really bad on my 'nuedel' machine!!! */
|
||||
in = *baseband + 0.000000001;
|
||||
for (j = 0; j < iterations; j++) {
|
||||
out = in * a0 + z1[j];
|
||||
#ifdef DEBUG_NAN
|
||||
if (!(out > -100 && out < 100)) {
|
||||
printf("%p\n", filter);
|
||||
printf("1. i=%d j=%d z=%.5f in=%.5f a0=%.5f out=%.5f\n", i, j, z1[j], in, a0, out);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
z1[j] = in * a1 + z2[j] - b1 * out;
|
||||
#ifdef DEBUG_NAN
|
||||
if (!(z1[j] > -100 && z1[j] < 100)) {
|
||||
printf("%p\n", filter);
|
||||
printf("2. i=%d j=%d z1=%.5f z2=%.5f in=%.5f a1=%.5f out=%.5f b1=%.5f\n", i, j, z1[j], z2[j], in, a1, out, b1);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
z2[j] = in * a2 - b2 * out;
|
||||
#ifdef DEBUG_NAN
|
||||
if (!(z2[j] > -100 && z2[j] < 100)) {
|
||||
printf("%p\n", filter);
|
||||
printf("%.5f\n", (in * a2) - (b2 * out));
|
||||
printf("3. i=%d j=%d z2=%.5f in=%.5f a2=%.5f b2=%.5f out=%.5f\n", i, j, z2[j], in, a2, b2, out);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
in = out;
|
||||
}
|
||||
*baseband = in;
|
||||
baseband += 2;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_NAN
|
||||
#pragma GCC pop_options
|
||||
#endif
|
|
@ -1,17 +0,0 @@
|
|||
#ifndef _FILTER_H
|
||||
#define _FILTER_H
|
||||
|
||||
typedef struct iir_filter {
|
||||
int iter;
|
||||
double a0, a1, a2, b1, b2;
|
||||
double z1[64], z2[64];
|
||||
} iir_filter_t;
|
||||
|
||||
void iir_lowpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations);
|
||||
void iir_highpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations);
|
||||
void iir_bandpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations);
|
||||
void iir_notch_init(iir_filter_t *filter, double frequency, int samplerate, int iterations);
|
||||
void iir_process(iir_filter_t *filter, sample_t *samples, int length);
|
||||
void iir_process_baseband(iir_filter_t *filter, float *baseband, int length);
|
||||
|
||||
#endif /* _FILTER_H */
|
|
@ -1,6 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libfm.a
|
||||
|
||||
libfm_a_SOURCES = \
|
||||
fm.c
|
|
@ -1,413 +0,0 @@
|
|||
/* FM modulation processing
|
||||
*
|
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "fm.h"
|
||||
|
||||
static int has_init = 0;
|
||||
static int fast_math = 0;
|
||||
static float *sin_tab = NULL, *cos_tab = NULL;
|
||||
|
||||
/* global init */
|
||||
int fm_init(int _fast_math)
|
||||
{
|
||||
fast_math = _fast_math;
|
||||
|
||||
if (fast_math) {
|
||||
int i;
|
||||
|
||||
sin_tab = calloc(65536+16384, sizeof(*sin_tab));
|
||||
if (!sin_tab) {
|
||||
fprintf(stderr, "No mem!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
cos_tab = sin_tab + 16384;
|
||||
|
||||
/* generate sine and cosine */
|
||||
for (i = 0; i < 65536+16384; i++)
|
||||
sin_tab[i] = sin(2.0 * M_PI * (double)i / 65536.0);
|
||||
}
|
||||
|
||||
has_init = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* global exit */
|
||||
void fm_exit(void)
|
||||
{
|
||||
if (sin_tab) {
|
||||
free(sin_tab);
|
||||
sin_tab = cos_tab = NULL;
|
||||
}
|
||||
|
||||
has_init = 0;
|
||||
}
|
||||
|
||||
/* init FM modulator */
|
||||
int fm_mod_init(fm_mod_t *mod, double samplerate, double offset, double amplitude)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!has_init) {
|
||||
fprintf(stderr, "libfm was not initialized, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
memset(mod, 0, sizeof(*mod));
|
||||
mod->samplerate = samplerate;
|
||||
mod->offset = offset;
|
||||
mod->amplitude = amplitude;
|
||||
|
||||
mod->ramp_length = samplerate * 0.001;
|
||||
mod->ramp_tab = calloc(mod->ramp_length, sizeof(*mod->ramp_tab));
|
||||
if (!mod->ramp_tab) {
|
||||
fprintf(stderr, "No mem!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
mod->state = MOD_STATE_OFF;
|
||||
|
||||
/* generate ramp up with ramp_length */
|
||||
for (i = 0; i < mod->ramp_length; i++)
|
||||
mod->ramp_tab[i] = 0.5 - cos(M_PI * i / mod->ramp_length) / 2.0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fm_mod_exit(fm_mod_t *mod)
|
||||
{
|
||||
if (mod->ramp_tab) {
|
||||
free(mod->ramp_tab);
|
||||
mod->ramp_tab = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* do frequency modulation of samples and add them to existing baseband */
|
||||
void fm_modulate_complex(fm_mod_t *mod, sample_t *frequency, uint8_t *power, int length, float *baseband)
|
||||
{
|
||||
double dev, rate, phase, offset;
|
||||
int ramp, ramp_length;
|
||||
double *ramp_tab;
|
||||
double amplitude;
|
||||
|
||||
rate = mod->samplerate;
|
||||
phase = mod->phase;
|
||||
offset = mod->offset;
|
||||
ramp = mod->ramp;
|
||||
ramp_length = mod->ramp_length;
|
||||
ramp_tab = mod->ramp_tab;
|
||||
amplitude = mod->amplitude;
|
||||
|
||||
again:
|
||||
switch (mod->state) {
|
||||
case MOD_STATE_ON:
|
||||
/* modulate */
|
||||
while (length) {
|
||||
/* is power is not set, ramp down */
|
||||
if (!(*power)) {
|
||||
mod->state = MOD_STATE_RAMP_DOWN;
|
||||
break;
|
||||
}
|
||||
/* deviation is defined by the frequency value and the offset */
|
||||
dev = offset + *frequency++;
|
||||
power++;
|
||||
length--;
|
||||
if (fast_math) {
|
||||
phase += 65536.0 * dev / rate;
|
||||
if (phase < 0.0)
|
||||
phase += 65536.0;
|
||||
else if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
*baseband++ += cos_tab[(uint16_t)phase] * amplitude;
|
||||
*baseband++ += sin_tab[(uint16_t)phase] * amplitude;
|
||||
} else {
|
||||
phase += 2.0 * M_PI * dev / rate;
|
||||
if (phase < 0.0)
|
||||
phase += 2.0 * M_PI;
|
||||
else if (phase >= 2.0 * M_PI)
|
||||
phase -= 2.0 * M_PI;
|
||||
*baseband++ += cos(phase) * amplitude;
|
||||
*baseband++ += sin(phase) * amplitude;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MOD_STATE_RAMP_DOWN:
|
||||
while (length) {
|
||||
/* if power is set, ramp up */
|
||||
if (*power) {
|
||||
mod->state = MOD_STATE_RAMP_UP;
|
||||
break;
|
||||
}
|
||||
if (ramp == 0) {
|
||||
mod->state = MOD_STATE_OFF;
|
||||
break;
|
||||
}
|
||||
dev = offset + *frequency++;
|
||||
power++;
|
||||
length--;
|
||||
if (fast_math) {
|
||||
phase += 65536.0 * dev / rate;
|
||||
if (phase < 0.0)
|
||||
phase += 65536.0;
|
||||
else if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
*baseband++ += cos_tab[(uint16_t)phase] * amplitude * ramp_tab[ramp];
|
||||
*baseband++ += sin_tab[(uint16_t)phase] * amplitude * ramp_tab[ramp];
|
||||
} else {
|
||||
phase += 2.0 * M_PI * dev / rate;
|
||||
if (phase < 0.0)
|
||||
phase += 2.0 * M_PI;
|
||||
else if (phase >= 2.0 * M_PI)
|
||||
phase -= 2.0 * M_PI;
|
||||
*baseband++ += cos(phase) * amplitude * ramp_tab[ramp];
|
||||
*baseband++ += sin(phase) * amplitude * ramp_tab[ramp];
|
||||
}
|
||||
ramp--;
|
||||
}
|
||||
break;
|
||||
case MOD_STATE_OFF:
|
||||
while (length) {
|
||||
/* if power is set, ramp up */
|
||||
if (*power) {
|
||||
mod->state = MOD_STATE_RAMP_UP;
|
||||
break;
|
||||
}
|
||||
/* just count, and add nothing */
|
||||
frequency++;
|
||||
power++;
|
||||
length--;
|
||||
baseband += 2;
|
||||
}
|
||||
break;
|
||||
case MOD_STATE_RAMP_UP:
|
||||
while (length) {
|
||||
/* is power is not set, ramp down */
|
||||
if (!(*power)) {
|
||||
mod->state = MOD_STATE_RAMP_DOWN;
|
||||
break;
|
||||
}
|
||||
if (ramp == ramp_length - 1) {
|
||||
mod->state = MOD_STATE_ON;
|
||||
break;
|
||||
}
|
||||
/* deviation is defined by the frequency value and the offset */
|
||||
dev = offset + *frequency++;
|
||||
power++;
|
||||
length--;
|
||||
if (fast_math) {
|
||||
phase += 65536.0 * dev / rate;
|
||||
if (phase < 0.0)
|
||||
phase += 65536.0;
|
||||
else if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
*baseband++ += cos_tab[(uint16_t)phase] * amplitude * ramp_tab[ramp];
|
||||
*baseband++ += sin_tab[(uint16_t)phase] * amplitude * ramp_tab[ramp];
|
||||
} else {
|
||||
phase += 2.0 * M_PI * dev / rate;
|
||||
if (phase < 0.0)
|
||||
phase += 2.0 * M_PI;
|
||||
else if (phase >= 2.0 * M_PI)
|
||||
phase -= 2.0 * M_PI;
|
||||
*baseband++ += cos(phase) * amplitude * ramp_tab[ramp];
|
||||
*baseband++ += sin(phase) * amplitude * ramp_tab[ramp];
|
||||
}
|
||||
ramp++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (length)
|
||||
goto again;
|
||||
|
||||
mod->phase = phase;
|
||||
mod->ramp = ramp;
|
||||
}
|
||||
|
||||
/* init FM demodulator */
|
||||
int fm_demod_init(fm_demod_t *demod, double samplerate, double offset, double bandwidth)
|
||||
{
|
||||
if (!has_init) {
|
||||
fprintf(stderr, "libfm was not initialized, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
memset(demod, 0, sizeof(*demod));
|
||||
demod->samplerate = samplerate;
|
||||
|
||||
if (fast_math)
|
||||
demod->rot = 65536.0 * -offset / samplerate;
|
||||
else
|
||||
demod->rot = 2 * M_PI * -offset / samplerate;
|
||||
|
||||
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
|
||||
iir_lowpass_init(&demod->lp[0], bandwidth / 2.0, samplerate, 2);
|
||||
iir_lowpass_init(&demod->lp[1], bandwidth / 2.0, samplerate, 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fm_demod_exit(fm_demod_t __attribute__ ((unused)) *demod)
|
||||
{
|
||||
}
|
||||
|
||||
static inline float fast_tan(float z)
|
||||
{
|
||||
const float n1 = 0.97239411f;
|
||||
const float n2 = -0.19194795f;
|
||||
return (n1 + n2 * z * z) * z;
|
||||
}
|
||||
|
||||
static inline float fast_atan2(float y, float x)
|
||||
{
|
||||
if (x != 0.0) {
|
||||
if (fabsf(x) > fabsf(y)) {
|
||||
const float z = y / x;
|
||||
if (x > 0.0) /* atan2(y,x) = atan(y/x) if x > 0 */
|
||||
return fast_tan(z);
|
||||
else if (y >= 0.0) /* atan2(y,x) = atan(y/x) + PI if x < 0, y >= 0 */
|
||||
return fast_tan(z) + M_PI;
|
||||
else /* atan2(y,x) = atan(y/x) - PI if x < 0, y < 0 */
|
||||
return fast_tan(z) - M_PI;
|
||||
} else { /* Use property atan(y/x) = PI/2 - atan(x/y) if |y/x| > 1 */
|
||||
const float z = x / y;
|
||||
if (y > 0.0) /* atan2(y,x) = PI/2 - atan(x/y) if |y/x| > 1, y > 0 */
|
||||
return -fast_tan(z) + M_PI_2;
|
||||
else /* atan2(y,x) = -PI/2 - atan(x/y) if |y/x| > 1, y < 0 */
|
||||
return -fast_tan(z) - M_PI_2;
|
||||
}
|
||||
} else {
|
||||
if (y > 0.0) /* x = 0, y > 0 */
|
||||
return M_PI_2;
|
||||
else if (y < 0.0) /* x = 0, y < 0 */
|
||||
return -M_PI_2;
|
||||
}
|
||||
return 0.0; /* x,y = 0. return 0, because NaN would harm further processing */
|
||||
}
|
||||
|
||||
/* do frequency demodulation of baseband and write them to samples */
|
||||
void fm_demodulate_complex(fm_demod_t *demod, sample_t *frequency, int length, float *baseband, sample_t *I, sample_t *Q)
|
||||
{
|
||||
double phase, rot, last_phase, dev, rate;
|
||||
double _sin, _cos;
|
||||
sample_t i, q;
|
||||
int s, ss;
|
||||
|
||||
rate = demod->samplerate;
|
||||
phase = demod->phase;
|
||||
rot = demod->rot;
|
||||
for (s = 0, ss = 0; s < length; s++) {
|
||||
phase += rot;
|
||||
i = baseband[ss++];
|
||||
q = baseband[ss++];
|
||||
if (fast_math) {
|
||||
if (phase < 0.0)
|
||||
phase += 65536.0;
|
||||
else if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
_sin = sin_tab[(uint16_t)phase];
|
||||
_cos = cos_tab[(uint16_t)phase];
|
||||
} else {
|
||||
if (phase < 0.0)
|
||||
phase += 2.0 * M_PI;
|
||||
else if (phase >= 2.0 * M_PI)
|
||||
phase -= 2.0 * M_PI;
|
||||
_sin = sin(phase);
|
||||
_cos = cos(phase);
|
||||
}
|
||||
I[s] = i * _cos - q * _sin;
|
||||
Q[s] = i * _sin + q * _cos;
|
||||
}
|
||||
demod->phase = phase;
|
||||
iir_process(&demod->lp[0], I, length);
|
||||
iir_process(&demod->lp[1], Q, length);
|
||||
last_phase = demod->last_phase;
|
||||
for (s = 0; s < length; s++) {
|
||||
if (fast_math)
|
||||
phase = fast_atan2(Q[s], I[s]);
|
||||
else
|
||||
phase = atan2(Q[s], I[s]);
|
||||
dev = (phase - last_phase) / 2 / M_PI;
|
||||
last_phase = phase;
|
||||
if (dev < -0.49)
|
||||
dev += 1.0;
|
||||
else if (dev > 0.49)
|
||||
dev -= 1.0;
|
||||
dev *= rate;
|
||||
frequency[s] = dev;
|
||||
}
|
||||
demod->last_phase = last_phase;
|
||||
}
|
||||
|
||||
void fm_demodulate_real(fm_demod_t *demod, sample_t *frequency, int length, sample_t *baseband, sample_t *I, sample_t *Q)
|
||||
{
|
||||
double phase, rot, last_phase, dev, rate;
|
||||
double _sin, _cos;
|
||||
sample_t i;
|
||||
int s, ss;
|
||||
|
||||
rate = demod->samplerate;
|
||||
phase = demod->phase;
|
||||
rot = demod->rot;
|
||||
for (s = 0, ss = 0; s < length; s++) {
|
||||
phase += rot;
|
||||
i = baseband[ss++];
|
||||
if (fast_math) {
|
||||
if (phase < 0.0)
|
||||
phase += 65536.0;
|
||||
else if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
_sin = sin_tab[(uint16_t)phase];
|
||||
_cos = cos_tab[(uint16_t)phase];
|
||||
} else {
|
||||
if (phase < 0.0)
|
||||
phase += 2.0 * M_PI;
|
||||
else if (phase >= 2.0 * M_PI)
|
||||
phase -= 2.0 * M_PI;
|
||||
_sin = sin(phase);
|
||||
_cos = cos(phase);
|
||||
}
|
||||
I[s] = i * _cos;
|
||||
Q[s] = i * _sin;
|
||||
}
|
||||
demod->phase = phase;
|
||||
iir_process(&demod->lp[0], I, length);
|
||||
iir_process(&demod->lp[1], Q, length);
|
||||
last_phase = demod->last_phase;
|
||||
for (s = 0; s < length; s++) {
|
||||
if (fast_math)
|
||||
phase = fast_atan2(Q[s], I[s]);
|
||||
else
|
||||
phase = atan2(Q[s], I[s]);
|
||||
dev = (phase - last_phase) / 2 / M_PI;
|
||||
last_phase = phase;
|
||||
if (dev < -0.49)
|
||||
dev += 1.0;
|
||||
else if (dev > 0.49)
|
||||
dev -= 1.0;
|
||||
dev *= rate;
|
||||
frequency[s] = dev;
|
||||
}
|
||||
demod->last_phase = last_phase;
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
#include "../libfilter/iir_filter.h"
|
||||
|
||||
int fm_init(int fast_math);
|
||||
void fm_exit(void);
|
||||
|
||||
enum fm_mod_state {
|
||||
MOD_STATE_OFF, /* transmitter off, no IQ vector */
|
||||
MOD_STATE_ON, /* transmitter on, FM modulated IQ vector */
|
||||
MOD_STATE_RAMP_UP, /* use half cos to ramp up IQ vector */
|
||||
MOD_STATE_RAMP_DOWN, /* use half cos to ramp down IQ vector */
|
||||
};
|
||||
|
||||
typedef struct fm_mod {
|
||||
double samplerate; /* sample rate of in and out */
|
||||
double offset; /* offset to calculated center frequency */
|
||||
double amplitude; /* how much amplitude to add to the buff */
|
||||
double phase; /* current phase of FM (used to shift and modulate ) */
|
||||
enum fm_mod_state state;/* state of transmit power */
|
||||
double *ramp_tab; /* half cosine ramp up */
|
||||
int ramp; /* current ramp position */
|
||||
int ramp_length; /* number of values in ramp */
|
||||
} fm_mod_t;
|
||||
|
||||
int fm_mod_init(fm_mod_t *mod, double samplerate, double offset, double amplitude);
|
||||
void fm_mod_exit(fm_mod_t *mod);
|
||||
void fm_modulate_complex(fm_mod_t *mod, sample_t *frequency, uint8_t *power, int num, float *baseband);
|
||||
|
||||
typedef struct fm_demod {
|
||||
double samplerate; /* sample rate of in and out */
|
||||
double phase; /* current rotation phase (used to shift) */
|
||||
double rot; /* rotation step per sample to shift rx frequency (used to shift) */
|
||||
double last_phase; /* last phase of FM (used to demodulate) */
|
||||
iir_filter_t lp[2]; /* filters received IQ signal */
|
||||
} fm_demod_t;
|
||||
|
||||
int fm_demod_init(fm_demod_t *demod, double samplerate, double offset, double bandwidth);
|
||||
void fm_demod_exit(fm_demod_t *demod);
|
||||
void fm_demodulate_complex(fm_demod_t *demod, sample_t *frequency, int length, float *baseband, sample_t *I, sample_t *Q);
|
||||
void fm_demodulate_real(fm_demod_t *demod, sample_t *frequency, int length, sample_t *baseband, sample_t *I, sample_t *Q);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libfsk.a
|
||||
|
||||
libfsk_a_SOURCES = \
|
||||
fsk.c
|
|
@ -1,363 +0,0 @@
|
|||
/* FSK audio processing (FSK/FFSK modem)
|
||||
*
|
||||
* (C) 2017 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 "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "fsk.h"
|
||||
|
||||
#define PI M_PI
|
||||
|
||||
/*
|
||||
* fsk = instance of fsk modem
|
||||
* inst = instance of user
|
||||
* send_bit() = function to be called whenever a new bit has to be sent
|
||||
* samplerate = samplerate
|
||||
* bitrate = bits per second
|
||||
* f0, f1 = two frequencies for bit 0 and bit 1
|
||||
* level = level to modulate the frequencies
|
||||
* ffsk = use FFSK modulation (each symbol ends at zero crossing)
|
||||
*/
|
||||
int fsk_mod_init(fsk_mod_t *fsk, void *inst, int (*send_bit)(void *inst), int samplerate, double bitrate, double f0, double f1, double level, int ffsk, int filter)
|
||||
{
|
||||
int i;
|
||||
int rc;
|
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Setup FSK for Transmitter. (F0 = %.1f, F1 = %.1f, peak = %.1f)\n", f0, f1, level);
|
||||
|
||||
memset(fsk, 0, sizeof(*fsk));
|
||||
|
||||
/* gen sine table with deviation */
|
||||
fsk->sin_tab = calloc(65536+16384, sizeof(*fsk->sin_tab));
|
||||
if (!fsk->sin_tab) {
|
||||
fprintf(stderr, "No mem!\n");
|
||||
rc = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
for (i = 0; i < 65536; i++)
|
||||
fsk->sin_tab[i] = sin((double)i / 65536.0 * 2.0 * PI) * level;
|
||||
|
||||
fsk->inst = inst;
|
||||
fsk->tx_bit = -1;
|
||||
fsk->level = level;
|
||||
fsk->send_bit = send_bit;
|
||||
fsk->f0_deviation = (f0 - f1) / 2.0;
|
||||
fsk->f1_deviation = (f1 - f0) / 2.0;
|
||||
if (f0 < f1) {
|
||||
fsk->low_bit = 0;
|
||||
fsk->high_bit = 1;
|
||||
} else {
|
||||
fsk->low_bit = 1;
|
||||
fsk->high_bit = 0;
|
||||
}
|
||||
|
||||
fsk->bits_per_sample = (double)bitrate / (double)samplerate;
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Bitduration of %.4f bits per sample @ %d.\n", fsk->bits_per_sample, samplerate);
|
||||
|
||||
fsk->phaseshift65536[0] = f0 / (double)samplerate * 65536.0;
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "F0 = %.0f Hz (phaseshift65536[0] = %.4f)\n", f0, fsk->phaseshift65536[0]);
|
||||
fsk->phaseshift65536[1] = f1 / (double)samplerate * 65536.0;
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "F1 = %.0f Hz (phaseshift65536[1] = %.4f)\n", f1, fsk->phaseshift65536[1]);
|
||||
|
||||
/* use ffsk modulation, i.e. each bit has an integer number of
|
||||
* half waves and starts/ends at zero crossing
|
||||
*/
|
||||
if (ffsk) {
|
||||
double waves;
|
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "enable FFSK modulation mode\n");
|
||||
fsk->ffsk = 1;
|
||||
waves = (f0 / bitrate);
|
||||
if (fabs(round(waves * 2) - (waves * 2)) > 0.001) {
|
||||
fprintf(stderr, "Failed to set FFSK mode, half waves of F0 does not fit exactly into one bit, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
fsk->cycles_per_bit65536[0] = waves * 65536.0;
|
||||
waves = (f1 / bitrate);
|
||||
if (fabs(round(waves * 2) - (waves * 2)) > 0.001) {
|
||||
fprintf(stderr, "Failed to set FFSK mode, half waves of F1 does not fit exactly into one bit, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
fsk->cycles_per_bit65536[1] = waves * 65536.0;
|
||||
}
|
||||
|
||||
/* if filter is enabled, add a band pass filter to smooth the spectrum of the tones
|
||||
* the bandwidth is twice the difference between f0 and f1
|
||||
*/
|
||||
if (filter) {
|
||||
double low = (f0 + f1) / 2.0 - fabs(f0 - f1);
|
||||
double high = (f0 + f1) / 2.0 + fabs(f0 - f1);
|
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "enable filter to smooth FSK transmission. (frequency rage %.0f .. %.0f)\n", low, high);
|
||||
fsk->filter = 1;
|
||||
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
|
||||
iir_highpass_init(&fsk->lp[0], low, samplerate, 2);
|
||||
iir_lowpass_init(&fsk->lp[1], high, samplerate, 2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
fsk_mod_cleanup(fsk);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Cleanup transceiver instance. */
|
||||
void fsk_mod_cleanup(fsk_mod_t *fsk)
|
||||
{
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Cleanup FSK for Transmitter.\n");
|
||||
|
||||
if (fsk->sin_tab) {
|
||||
free(fsk->sin_tab);
|
||||
fsk->sin_tab = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* modulate bits
|
||||
*
|
||||
* If first/next bit is required, callback function send_bit() is called.
|
||||
* If there is no (more) data to be transmitted, the callback functions shall
|
||||
* return -1. In this case, this function stops and returns the number of
|
||||
* samples that have been rendered so far, if any.
|
||||
*
|
||||
* For FFSK mode, we round the phase on every bit change to the
|
||||
* next zero crossing. This prevents phase shifts due to rounding errors.
|
||||
*/
|
||||
int fsk_mod_send(fsk_mod_t *fsk, sample_t *sample, int length, int add)
|
||||
{
|
||||
int count = 0;
|
||||
double phase, phaseshift;
|
||||
|
||||
phase = fsk->tx_phase65536;
|
||||
|
||||
/* get next bit */
|
||||
if (fsk->tx_bit < 0) {
|
||||
next_bit:
|
||||
fsk->tx_bit = fsk->send_bit(fsk->inst);
|
||||
#ifdef DEBUG_MODULATOR
|
||||
printf("bit change to %d\n", fsk->tx_bit);
|
||||
#endif
|
||||
if (fsk->tx_bit < 0)
|
||||
goto done;
|
||||
/* correct phase when changing bit */
|
||||
if (fsk->ffsk) {
|
||||
/* round phase to nearest zero crossing */
|
||||
if (phase > 16384.0 && phase < 49152.0)
|
||||
phase = 32768.0;
|
||||
else
|
||||
phase = 0;
|
||||
/* set phase according to current position in bit */
|
||||
phase += fsk->tx_bitpos * fsk->cycles_per_bit65536[fsk->tx_bit & 1];
|
||||
#ifdef DEBUG_MODULATOR
|
||||
printf("phase %.3f bitpos=%.6f\n", phase, fsk->tx_bitpos);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* modulate bit */
|
||||
phaseshift = fsk->phaseshift65536[fsk->tx_bit & 1];
|
||||
while (count < length && fsk->tx_bitpos < 1.0) {
|
||||
if (add)
|
||||
sample[count++] += fsk->sin_tab[(uint16_t)phase];
|
||||
else
|
||||
sample[count++] = fsk->sin_tab[(uint16_t)phase];
|
||||
#ifdef DEBUG_MODULATOR
|
||||
printf("|%s|\n", debug_amplitude(fsk->sin_tab[(uint16_t)phase] / fsk->level));
|
||||
#endif
|
||||
phase += phaseshift;
|
||||
if (phase >= 65536.0)
|
||||
phase -= 65536.0;
|
||||
fsk->tx_bitpos += fsk->bits_per_sample;
|
||||
}
|
||||
if (fsk->tx_bitpos >= 1.0) {
|
||||
fsk->tx_bitpos -= 1.0;
|
||||
goto next_bit;
|
||||
}
|
||||
|
||||
/* post filter */
|
||||
if (fsk->filter) {
|
||||
iir_process(&fsk->lp[0], sample, length);
|
||||
iir_process(&fsk->lp[1], sample, length);
|
||||
}
|
||||
|
||||
done:
|
||||
fsk->tx_phase65536 = phase;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* reset transmitter state, so we get a clean start */
|
||||
void fsk_mod_tx_reset(fsk_mod_t *fsk)
|
||||
{
|
||||
fsk->tx_phase65536 = 0;
|
||||
fsk->tx_bitpos = 0;
|
||||
fsk->tx_bit = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* fsk = instance of fsk modem
|
||||
* inst = instance of user
|
||||
* receive_bit() = function to be called whenever a new bit was received
|
||||
* samplerate = samplerate
|
||||
* bitrate = bits per second
|
||||
* f0, f1 = two frequencies for bit 0 and bit 1
|
||||
* bitadjust = how much to adjust the sample clock when a bitchange was detected. (0 = nothing, don't use this, 0.5 full adjustment)
|
||||
*/
|
||||
int fsk_demod_init(fsk_demod_t *fsk, void *inst, void (*receive_bit)(void *inst, int bit, double quality, double level), int samplerate, double bitrate, double f0, double f1, double bitadjust)
|
||||
{
|
||||
double bandwidth;
|
||||
int rc;
|
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Setup FSK for Receiver. (F0 = %.1f, F1 = %.1f)\n", f0, f1);
|
||||
|
||||
memset(fsk, 0, sizeof(*fsk));
|
||||
|
||||
fsk->inst = inst;
|
||||
fsk->rx_bit = -1;
|
||||
fsk->rx_bitadjust = bitadjust;
|
||||
fsk->receive_bit = receive_bit;
|
||||
fsk->f0_deviation = (f0 - f1) / 2.0;
|
||||
fsk->f1_deviation = (f1 - f0) / 2.0;
|
||||
if (f0 < f1) {
|
||||
fsk->low_bit = 0;
|
||||
fsk->high_bit = 1;
|
||||
} else {
|
||||
fsk->low_bit = 1;
|
||||
fsk->high_bit = 0;
|
||||
}
|
||||
|
||||
/* calculate bandwidth */
|
||||
bandwidth = fabs(f0 - f1) * 2.0;
|
||||
|
||||
/* init fm demodulator */
|
||||
rc = fm_demod_init(&fsk->demod, (double)samplerate, (f0 + f1) / 2.0, bandwidth);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
|
||||
fsk->bits_per_sample = (double)bitrate / (double)samplerate;
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Bitduration of %.4f bits per sample @ %d.\n", fsk->bits_per_sample, samplerate);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
fsk_demod_cleanup(fsk);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Cleanup transceiver instance. */
|
||||
void fsk_demod_cleanup(fsk_demod_t *fsk)
|
||||
{
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Cleanup FSK for Receiver.\n");
|
||||
|
||||
fm_demod_exit(&fsk->demod);
|
||||
}
|
||||
|
||||
//#define DEBUG_MODULATOR
|
||||
//#define DEBUG_FILTER
|
||||
|
||||
/* Demodulates bits
|
||||
*
|
||||
* If bit is received, callback function send_bit() is called.
|
||||
*
|
||||
* We sample each bit 0.5 bits after polarity change.
|
||||
*
|
||||
* If we have a bit change, adjust sample counter towards one half bit duration.
|
||||
* We may have noise, so the bit change may be wrong or not at the correct place.
|
||||
* This can cause bit slips.
|
||||
* Therefore we change the sample counter only slightly, so bit slips may not
|
||||
* happen so quickly.
|
||||
*/
|
||||
void fsk_demod_receive(fsk_demod_t *fsk, sample_t *sample, int length)
|
||||
{
|
||||
sample_t I[length], Q[length], frequency[length], f;
|
||||
int i;
|
||||
int bit;
|
||||
double level, quality;
|
||||
|
||||
/* demod samples to offset around center frequency */
|
||||
fm_demodulate_real(&fsk->demod, frequency, length, sample, I, Q);
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
f = frequency[i];
|
||||
if (f < 0)
|
||||
bit = fsk->low_bit;
|
||||
else
|
||||
bit = fsk->high_bit;
|
||||
#ifdef DEBUG_FILTER
|
||||
printf("|%s| %.3f\n", debug_amplitude(f / fabs(fsk->f0_deviation) / 2), f / fabs(fsk->f0_deviation));
|
||||
#endif
|
||||
|
||||
|
||||
if (fsk->rx_bit != bit) {
|
||||
#ifdef DEBUG_FILTER
|
||||
puts("bit change");
|
||||
#endif
|
||||
fsk->rx_bit = bit;
|
||||
if (fsk->rx_bitpos < 0.5) {
|
||||
fsk->rx_bitpos += fsk->rx_bitadjust;
|
||||
if (fsk->rx_bitpos > 0.5)
|
||||
fsk->rx_bitpos = 0.5;
|
||||
} else
|
||||
if (fsk->rx_bitpos > 0.5) {
|
||||
fsk->rx_bitpos -= fsk->rx_bitadjust;
|
||||
if (fsk->rx_bitpos < 0.5)
|
||||
fsk->rx_bitpos = 0.5;
|
||||
}
|
||||
/* if we have a pulse before we sampled a bit after last pulse */
|
||||
if (fsk->rx_change) {
|
||||
/* peak level is the length of I/Q vector
|
||||
* since we filter out the unwanted modulation product, the vector is only half of length */
|
||||
level = sqrt(I[i] * I[i] + Q[i] * Q[i]) * 2.0;
|
||||
#ifdef DEBUG_FILTER
|
||||
printf("prematurely bit change (level=%.3f)\n", level);
|
||||
#endif
|
||||
/* quality is 0.0, because a prematurely level change is caused by noise and has nothing to measure. */
|
||||
fsk->receive_bit(fsk->inst, fsk->rx_bit, 0.0, level);
|
||||
}
|
||||
fsk->rx_change = 1;
|
||||
}
|
||||
/* if bit counter reaches 1, we subtract 1 and sample the bit */
|
||||
if (fsk->rx_bitpos >= 1.0) {
|
||||
/* peak level is the length of I/Q vector
|
||||
* since we filter out the unwanted modulation product, the vector is only half of length */
|
||||
level = sqrt(I[i] * I[i] + Q[i] * Q[i]) * 2.0;
|
||||
/* quality is defined on how accurat the target frequency it hit
|
||||
* if it is hit close to the center or close to double deviation from center, quality is close to 0 */
|
||||
if (bit == 0)
|
||||
quality = 1.0 - fabs((f - fsk->f0_deviation) / fsk->f0_deviation);
|
||||
else
|
||||
quality = 1.0 - fabs((f - fsk->f1_deviation) / fsk->f1_deviation);
|
||||
if (quality < 0)
|
||||
quality = 0;
|
||||
#ifdef DEBUG_FILTER
|
||||
printf("sample (level=%.3f, quality=%.3f)\n", level, quality);
|
||||
#endif
|
||||
fsk->receive_bit(fsk->inst, bit, quality, level);
|
||||
fsk->rx_bitpos -= 1.0;
|
||||
fsk->rx_change = 0;
|
||||
}
|
||||
fsk->rx_bitpos += fsk->bits_per_sample;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
#include "../libfm/fm.h"
|
||||
|
||||
typedef struct fsk_mod {
|
||||
void *inst;
|
||||
int (*send_bit)(void *inst);
|
||||
double bits_per_sample; /* fraction of a bit per sample */
|
||||
double *sin_tab; /* sine table with correct peak level */
|
||||
double phaseshift65536[2]; /* how much the phase of fsk synbol changes per sample */
|
||||
double cycles_per_bit65536[2]; /* cycles of one bit */
|
||||
double tx_phase65536; /* current transmit phase */
|
||||
double level; /* level (amplitude) of signal */
|
||||
int ffsk; /* set, if FFSK TX mode */
|
||||
double f0_deviation; /* deviation of frequencies, relative to center */
|
||||
double f1_deviation;
|
||||
int low_bit, high_bit; /* a low or high deviation means which bit? */
|
||||
int tx_bit; /* current transmitting bit (-1 if not set) */
|
||||
double tx_bitpos; /* current transmit position in bit */
|
||||
int filter; /* set, if filters are used */
|
||||
iir_filter_t lp[2]; /* filter to smooth transmission spectrum */
|
||||
} fsk_mod_t;
|
||||
|
||||
typedef struct fsk_demod {
|
||||
void *inst;
|
||||
void (*receive_bit)(void *inst, int bit, double quality, double level);
|
||||
fm_demod_t demod;
|
||||
double bits_per_sample; /* fraction of a bit per sample */
|
||||
double f0_deviation; /* deviation of frequencies, relative to center */
|
||||
double f1_deviation;
|
||||
int low_bit, high_bit; /* a low or high deviation means which bit? */
|
||||
int rx_bit; /* current receiving bit (-1 if not yet measured) */
|
||||
double rx_bitpos; /* current receive position in bit (sampleclock) */
|
||||
double rx_bitadjust; /* how much does a bit change cause the sample clock to be adjusted in phase */
|
||||
int rx_change; /* set, if we have a level change before sampling the bit */
|
||||
} fsk_demod_t;
|
||||
|
||||
int fsk_mod_init(fsk_mod_t *fsk, void *inst, int (*send_bit)(void *inst), int samplerate, double bitrate, double f0, double f1, double level, int coherent, int filter);
|
||||
void fsk_mod_cleanup(fsk_mod_t *fsk);
|
||||
int fsk_mod_send(fsk_mod_t *fsk, sample_t *sample, int length, int add);
|
||||
void fsk_mod_tx_reset(fsk_mod_t *fsk);
|
||||
int fsk_demod_init(fsk_demod_t *fsk, void *inst, void (*receive_bit)(void *inst, int bit, double quality, double level), int samplerate, double bitrate, double f0, double f1, double bitadjust);
|
||||
void fsk_demod_cleanup(fsk_demod_t *fsk);
|
||||
void fsk_demod_receive(fsk_demod_t *fsk, sample_t *sample, int length);
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libg711.a
|
||||
|
||||
libg711_a_SOURCES = \
|
||||
g711.c
|
||||
|
|
@ -1,537 +0,0 @@
|
|||
/*****************************************************************************\
|
||||
** **
|
||||
** PBX4Linux **
|
||||
** **
|
||||
**---------------------------------------------------------------------------**
|
||||
** Copyright: Andreas Eversberg (GPL) **
|
||||
** **
|
||||
** audio conversions for alaw and ulaw **
|
||||
** **
|
||||
\*****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* ulaw -> signed 16-bit */
|
||||
static int16_t g711_ulaw_flipped_to_linear[256] =
|
||||
{
|
||||
0x8284, 0x8684, 0x8a84, 0x8e84, 0x9284, 0x9684, 0x9a84, 0x9e84,
|
||||
0xa284, 0xa684, 0xaa84, 0xae84, 0xb284, 0xb684, 0xba84, 0xbe84,
|
||||
0xc184, 0xc384, 0xc584, 0xc784, 0xc984, 0xcb84, 0xcd84, 0xcf84,
|
||||
0xd184, 0xd384, 0xd584, 0xd784, 0xd984, 0xdb84, 0xdd84, 0xdf84,
|
||||
0xe104, 0xe204, 0xe304, 0xe404, 0xe504, 0xe604, 0xe704, 0xe804,
|
||||
0xe904, 0xea04, 0xeb04, 0xec04, 0xed04, 0xee04, 0xef04, 0xf004,
|
||||
0xf0c4, 0xf144, 0xf1c4, 0xf244, 0xf2c4, 0xf344, 0xf3c4, 0xf444,
|
||||
0xf4c4, 0xf544, 0xf5c4, 0xf644, 0xf6c4, 0xf744, 0xf7c4, 0xf844,
|
||||
0xf8a4, 0xf8e4, 0xf924, 0xf964, 0xf9a4, 0xf9e4, 0xfa24, 0xfa64,
|
||||
0xfaa4, 0xfae4, 0xfb24, 0xfb64, 0xfba4, 0xfbe4, 0xfc24, 0xfc64,
|
||||
0xfc94, 0xfcb4, 0xfcd4, 0xfcf4, 0xfd14, 0xfd34, 0xfd54, 0xfd74,
|
||||
0xfd94, 0xfdb4, 0xfdd4, 0xfdf4, 0xfe14, 0xfe34, 0xfe54, 0xfe74,
|
||||
0xfe8c, 0xfe9c, 0xfeac, 0xfebc, 0xfecc, 0xfedc, 0xfeec, 0xfefc,
|
||||
0xff0c, 0xff1c, 0xff2c, 0xff3c, 0xff4c, 0xff5c, 0xff6c, 0xff7c,
|
||||
0xff88, 0xff90, 0xff98, 0xffa0, 0xffa8, 0xffb0, 0xffb8, 0xffc0,
|
||||
0xffc8, 0xffd0, 0xffd8, 0xffe0, 0xffe8, 0xfff0, 0xfff8, 0xffff,
|
||||
0x7d7c, 0x797c, 0x757c, 0x717c, 0x6d7c, 0x697c, 0x657c, 0x617c,
|
||||
0x5d7c, 0x597c, 0x557c, 0x517c, 0x4d7c, 0x497c, 0x457c, 0x417c,
|
||||
0x3e7c, 0x3c7c, 0x3a7c, 0x387c, 0x367c, 0x347c, 0x327c, 0x307c,
|
||||
0x2e7c, 0x2c7c, 0x2a7c, 0x287c, 0x267c, 0x247c, 0x227c, 0x207c,
|
||||
0x1efc, 0x1dfc, 0x1cfc, 0x1bfc, 0x1afc, 0x19fc, 0x18fc, 0x17fc,
|
||||
0x16fc, 0x15fc, 0x14fc, 0x13fc, 0x12fc, 0x11fc, 0x10fc, 0x0ffc,
|
||||
0x0f3c, 0x0ebc, 0x0e3c, 0x0dbc, 0x0d3c, 0x0cbc, 0x0c3c, 0x0bbc,
|
||||
0x0b3c, 0x0abc, 0x0a3c, 0x09bc, 0x093c, 0x08bc, 0x083c, 0x07bc,
|
||||
0x075c, 0x071c, 0x06dc, 0x069c, 0x065c, 0x061c, 0x05dc, 0x059c,
|
||||
0x055c, 0x051c, 0x04dc, 0x049c, 0x045c, 0x041c, 0x03dc, 0x039c,
|
||||
0x036c, 0x034c, 0x032c, 0x030c, 0x02ec, 0x02cc, 0x02ac, 0x028c,
|
||||
0x026c, 0x024c, 0x022c, 0x020c, 0x01ec, 0x01cc, 0x01ac, 0x018c,
|
||||
0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104,
|
||||
0x00f4, 0x00e4, 0x00d4, 0x00c4, 0x00b4, 0x00a4, 0x0094, 0x0084,
|
||||
0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040,
|
||||
0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000
|
||||
};
|
||||
|
||||
/* alaw -> signed 16-bit */
|
||||
static int16_t g711_alaw_flipped_to_linear[256] =
|
||||
{
|
||||
0x13fc, 0xec04, 0x0144, 0xfebc, 0x517c, 0xae84, 0x051c, 0xfae4,
|
||||
0x0a3c, 0xf5c4, 0x0048, 0xffb8, 0x287c, 0xd784, 0x028c, 0xfd74,
|
||||
0x1bfc, 0xe404, 0x01cc, 0xfe34, 0x717c, 0x8e84, 0x071c, 0xf8e4,
|
||||
0x0e3c, 0xf1c4, 0x00c4, 0xff3c, 0x387c, 0xc784, 0x039c, 0xfc64,
|
||||
0x0ffc, 0xf004, 0x0104, 0xfefc, 0x417c, 0xbe84, 0x041c, 0xfbe4,
|
||||
0x083c, 0xf7c4, 0x0008, 0xfff8, 0x207c, 0xdf84, 0x020c, 0xfdf4,
|
||||
0x17fc, 0xe804, 0x018c, 0xfe74, 0x617c, 0x9e84, 0x061c, 0xf9e4,
|
||||
0x0c3c, 0xf3c4, 0x0084, 0xff7c, 0x307c, 0xcf84, 0x030c, 0xfcf4,
|
||||
0x15fc, 0xea04, 0x0164, 0xfe9c, 0x597c, 0xa684, 0x059c, 0xfa64,
|
||||
0x0b3c, 0xf4c4, 0x0068, 0xff98, 0x2c7c, 0xd384, 0x02cc, 0xfd34,
|
||||
0x1dfc, 0xe204, 0x01ec, 0xfe14, 0x797c, 0x8684, 0x07bc, 0xf844,
|
||||
0x0f3c, 0xf0c4, 0x00e4, 0xff1c, 0x3c7c, 0xc384, 0x03dc, 0xfc24,
|
||||
0x11fc, 0xee04, 0x0124, 0xfedc, 0x497c, 0xb684, 0x049c, 0xfb64,
|
||||
0x093c, 0xf6c4, 0x0028, 0xffd8, 0x247c, 0xdb84, 0x024c, 0xfdb4,
|
||||
0x19fc, 0xe604, 0x01ac, 0xfe54, 0x697c, 0x9684, 0x069c, 0xf964,
|
||||
0x0d3c, 0xf2c4, 0x00a4, 0xff5c, 0x347c, 0xcb84, 0x034c, 0xfcb4,
|
||||
0x12fc, 0xed04, 0x0134, 0xfecc, 0x4d7c, 0xb284, 0x04dc, 0xfb24,
|
||||
0x09bc, 0xf644, 0x0038, 0xffc8, 0x267c, 0xd984, 0x026c, 0xfd94,
|
||||
0x1afc, 0xe504, 0x01ac, 0xfe54, 0x6d7c, 0x9284, 0x06dc, 0xf924,
|
||||
0x0dbc, 0xf244, 0x00b4, 0xff4c, 0x367c, 0xc984, 0x036c, 0xfc94,
|
||||
0x0f3c, 0xf0c4, 0x00f4, 0xff0c, 0x3e7c, 0xc184, 0x03dc, 0xfc24,
|
||||
0x07bc, 0xf844, 0x0008, 0xfff8, 0x1efc, 0xe104, 0x01ec, 0xfe14,
|
||||
0x16fc, 0xe904, 0x0174, 0xfe8c, 0x5d7c, 0xa284, 0x05dc, 0xfa24,
|
||||
0x0bbc, 0xf444, 0x0078, 0xff88, 0x2e7c, 0xd184, 0x02ec, 0xfd14,
|
||||
0x14fc, 0xeb04, 0x0154, 0xfeac, 0x557c, 0xaa84, 0x055c, 0xfaa4,
|
||||
0x0abc, 0xf544, 0x0058, 0xffa8, 0x2a7c, 0xd584, 0x02ac, 0xfd54,
|
||||
0x1cfc, 0xe304, 0x01cc, 0xfe34, 0x757c, 0x8a84, 0x075c, 0xf8a4,
|
||||
0x0ebc, 0xf144, 0x00d4, 0xff2c, 0x3a7c, 0xc584, 0x039c, 0xfc64,
|
||||
0x10fc, 0xef04, 0x0114, 0xfeec, 0x457c, 0xba84, 0x045c, 0xfba4,
|
||||
0x08bc, 0xf744, 0x0018, 0xffe8, 0x227c, 0xdd84, 0x022c, 0xfdd4,
|
||||
0x18fc, 0xe704, 0x018c, 0xfe74, 0x657c, 0x9a84, 0x065c, 0xf9a4,
|
||||
0x0cbc, 0xf344, 0x0094, 0xff6c, 0x327c, 0xcd84, 0x032c, 0xfcd4
|
||||
};
|
||||
|
||||
/* Xlaw -> signed 16-bit */
|
||||
static int16_t g711_alaw_to_linear[256];
|
||||
static int16_t g711_ulaw_to_linear[256];
|
||||
|
||||
/* signed 16-bit -> Xlaw */
|
||||
static uint8_t g711_linear_to_alaw_flipped[65536];
|
||||
static uint8_t g711_linear_to_ulaw_flipped[65536];
|
||||
static uint8_t g711_linear_to_alaw[65536];
|
||||
static uint8_t g711_linear_to_ulaw[65536];
|
||||
|
||||
/* transcode */
|
||||
static uint8_t g711_alaw_to_ulaw[256];
|
||||
static uint8_t g711_ulaw_to_alaw[256];
|
||||
static uint8_t g711_alaw_flipped_to_ulaw[256];
|
||||
static uint8_t g711_ulaw_flipped_to_alaw[256];
|
||||
static uint8_t g711_alaw_to_ulaw_flipped[256];
|
||||
static uint8_t g711_ulaw_to_alaw_flipped[256];
|
||||
|
||||
/* table is used to generate linear_to_alaw */
|
||||
static int16_t g711_alaw_relations[] =
|
||||
{
|
||||
0x8684, 0x55, 0x8a84, 0xd5, 0x8e84, 0x15, 0x9284, 0x95,
|
||||
0x9684, 0x75, 0x9a84, 0xf5, 0x9e84, 0x35, 0xa284, 0xb5,
|
||||
0xa684, 0x45, 0xaa84, 0xc5, 0xae84, 0x05, 0xb284, 0x85,
|
||||
0xb684, 0x65, 0xba84, 0xe5, 0xbe84, 0x25, 0xc184, 0xa5,
|
||||
0xc384, 0x5d, 0xc584, 0xdd, 0xc784, 0x1d, 0xc984, 0x9d,
|
||||
0xcb84, 0x7d, 0xcd84, 0xfd, 0xcf84, 0x3d, 0xd184, 0xbd,
|
||||
0xd384, 0x4d, 0xd584, 0xcd, 0xd784, 0x0d, 0xd984, 0x8d,
|
||||
0xdb84, 0x6d, 0xdd84, 0xed, 0xdf84, 0x2d, 0xe104, 0xad,
|
||||
0xe204, 0x51, 0xe304, 0xd1, 0xe404, 0x11, 0xe504, 0x91,
|
||||
0xe604, 0x71, 0xe704, 0xf1, 0xe804, 0x31, 0xe904, 0xb1,
|
||||
0xea04, 0x41, 0xeb04, 0xc1, 0xec04, 0x01, 0xed04, 0x81,
|
||||
0xee04, 0x61, 0xef04, 0xe1, 0xf004, 0x21, 0xf0c4, 0x59,
|
||||
0xf0c4, 0xa1, 0xf144, 0xd9, 0xf1c4, 0x19, 0xf244, 0x99,
|
||||
0xf2c4, 0x79, 0xf344, 0xf9, 0xf3c4, 0x39, 0xf444, 0xb9,
|
||||
0xf4c4, 0x49, 0xf544, 0xc9, 0xf5c4, 0x09, 0xf644, 0x89,
|
||||
0xf6c4, 0x69, 0xf744, 0xe9, 0xf7c4, 0x29, 0xf844, 0x57,
|
||||
0xf844, 0xa9, 0xf8a4, 0xd7, 0xf8e4, 0x17, 0xf924, 0x97,
|
||||
0xf964, 0x77, 0xf9a4, 0xf7, 0xf9e4, 0x37, 0xfa24, 0xb7,
|
||||
0xfa64, 0x47, 0xfaa4, 0xc7, 0xfae4, 0x07, 0xfb24, 0x87,
|
||||
0xfb64, 0x67, 0xfba4, 0xe7, 0xfbe4, 0x27, 0xfc24, 0x5f,
|
||||
0xfc24, 0xa7, 0xfc64, 0x1f, 0xfc64, 0xdf, 0xfc94, 0x9f,
|
||||
0xfcb4, 0x7f, 0xfcd4, 0xff, 0xfcf4, 0x3f, 0xfd14, 0xbf,
|
||||
0xfd34, 0x4f, 0xfd54, 0xcf, 0xfd74, 0x0f, 0xfd94, 0x8f,
|
||||
0xfdb4, 0x6f, 0xfdd4, 0xef, 0xfdf4, 0x2f, 0xfe14, 0x53,
|
||||
0xfe14, 0xaf, 0xfe34, 0x13, 0xfe34, 0xd3, 0xfe54, 0x73,
|
||||
0xfe54, 0x93, 0xfe74, 0x33, 0xfe74, 0xf3, 0xfe8c, 0xb3,
|
||||
0xfe9c, 0x43, 0xfeac, 0xc3, 0xfebc, 0x03, 0xfecc, 0x83,
|
||||
0xfedc, 0x63, 0xfeec, 0xe3, 0xfefc, 0x23, 0xff0c, 0xa3,
|
||||
0xff1c, 0x5b, 0xff2c, 0xdb, 0xff3c, 0x1b, 0xff4c, 0x9b,
|
||||
0xff5c, 0x7b, 0xff6c, 0xfb, 0xff7c, 0x3b, 0xff88, 0xbb,
|
||||
0xff98, 0x4b, 0xffa8, 0xcb, 0xffb8, 0x0b, 0xffc8, 0x8b,
|
||||
0xffd8, 0x6b, 0xffe8, 0xeb, 0xfff8, 0x2b, 0xfff8, 0xab,
|
||||
0x0008, 0x2a, 0x0008, 0xaa, 0x0018, 0xea, 0x0028, 0x6a,
|
||||
0x0038, 0x8a, 0x0048, 0x0a, 0x0058, 0xca, 0x0068, 0x4a,
|
||||
0x0078, 0xba, 0x0084, 0x3a, 0x0094, 0xfa, 0x00a4, 0x7a,
|
||||
0x00b4, 0x9a, 0x00c4, 0x1a, 0x00d4, 0xda, 0x00e4, 0x5a,
|
||||
0x00f4, 0xa2, 0x0104, 0x22, 0x0114, 0xe2, 0x0124, 0x62,
|
||||
0x0134, 0x82, 0x0144, 0x02, 0x0154, 0xc2, 0x0164, 0x42,
|
||||
0x0174, 0xb2, 0x018c, 0x32, 0x018c, 0xf2, 0x01ac, 0x72,
|
||||
0x01ac, 0x92, 0x01cc, 0x12, 0x01cc, 0xd2, 0x01ec, 0x52,
|
||||
0x01ec, 0xae, 0x020c, 0x2e, 0x022c, 0xee, 0x024c, 0x6e,
|
||||
0x026c, 0x8e, 0x028c, 0x0e, 0x02ac, 0xce, 0x02cc, 0x4e,
|
||||
0x02ec, 0xbe, 0x030c, 0x3e, 0x032c, 0xfe, 0x034c, 0x7e,
|
||||
0x036c, 0x9e, 0x039c, 0x1e, 0x039c, 0xde, 0x03dc, 0x5e,
|
||||
0x03dc, 0xa6, 0x041c, 0x26, 0x045c, 0xe6, 0x049c, 0x66,
|
||||
0x04dc, 0x86, 0x051c, 0x06, 0x055c, 0xc6, 0x059c, 0x46,
|
||||
0x05dc, 0xb6, 0x061c, 0x36, 0x065c, 0xf6, 0x069c, 0x76,
|
||||
0x06dc, 0x96, 0x071c, 0x16, 0x075c, 0xd6, 0x07bc, 0x56,
|
||||
0x07bc, 0xa8, 0x083c, 0x28, 0x08bc, 0xe8, 0x093c, 0x68,
|
||||
0x09bc, 0x88, 0x0a3c, 0x08, 0x0abc, 0xc8, 0x0b3c, 0x48,
|
||||
0x0bbc, 0xb8, 0x0c3c, 0x38, 0x0cbc, 0xf8, 0x0d3c, 0x78,
|
||||
0x0dbc, 0x98, 0x0e3c, 0x18, 0x0ebc, 0xd8, 0x0f3c, 0x58,
|
||||
0x0f3c, 0xa0, 0x0ffc, 0x20, 0x10fc, 0xe0, 0x11fc, 0x60,
|
||||
0x12fc, 0x80, 0x13fc, 0x00, 0x14fc, 0xc0, 0x15fc, 0x40,
|
||||
0x16fc, 0xb0, 0x17fc, 0x30, 0x18fc, 0xf0, 0x19fc, 0x70,
|
||||
0x1afc, 0x90, 0x1bfc, 0x10, 0x1cfc, 0xd0, 0x1dfc, 0x50,
|
||||
0x1efc, 0xac, 0x207c, 0x2c, 0x227c, 0xec, 0x247c, 0x6c,
|
||||
0x267c, 0x8c, 0x287c, 0x0c, 0x2a7c, 0xcc, 0x2c7c, 0x4c,
|
||||
0x2e7c, 0xbc, 0x307c, 0x3c, 0x327c, 0xfc, 0x347c, 0x7c,
|
||||
0x367c, 0x9c, 0x387c, 0x1c, 0x3a7c, 0xdc, 0x3c7c, 0x5c,
|
||||
0x3e7c, 0xa4, 0x417c, 0x24, 0x457c, 0xe4, 0x497c, 0x64,
|
||||
0x4d7c, 0x84, 0x517c, 0x04, 0x557c, 0xc4, 0x597c, 0x44,
|
||||
0x5d7c, 0xb4, 0x617c, 0x34, 0x657c, 0xf4, 0x697c, 0x74,
|
||||
0x6d7c, 0x94, 0x717c, 0x14, 0x757c, 0xd4, 0x797c, 0x54
|
||||
};
|
||||
|
||||
uint8_t g711_flip[256];
|
||||
|
||||
static int g711_initialized = 0;
|
||||
|
||||
/* generate tables
|
||||
*/
|
||||
void g711_init(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
/* flip tables */
|
||||
for (i = 0; i < 256; i++) {
|
||||
g711_flip[i]
|
||||
= ((i & 1) << 7)
|
||||
+ ((i & 2) << 5)
|
||||
+ ((i & 4) << 3)
|
||||
+ ((i & 8) << 1)
|
||||
+ ((i & 16) >> 1)
|
||||
+ ((i & 32) >> 3)
|
||||
+ ((i & 64) >> 5)
|
||||
+ ((i & 128) >> 7);
|
||||
g711_alaw_to_linear[i] = g711_alaw_flipped_to_linear[g711_flip[i]];
|
||||
g711_ulaw_to_linear[i] = g711_ulaw_flipped_to_linear[g711_flip[i]];
|
||||
}
|
||||
|
||||
/* linear to alaw tables */
|
||||
i = j = 0;
|
||||
while(i < 65536) {
|
||||
if (i - 32768 > g711_alaw_relations[j << 1])
|
||||
j++;
|
||||
if (j > 255)
|
||||
j = 255;
|
||||
g711_linear_to_alaw_flipped[(i - 32768) & 0xffff] = g711_alaw_relations[(j << 1) | 1];
|
||||
g711_linear_to_alaw[(i - 32768) & 0xffff] = g711_flip[g711_alaw_relations[(j << 1) | 1]];
|
||||
i++;
|
||||
}
|
||||
|
||||
/* linear to ulaw tables */
|
||||
i = j = 0;
|
||||
while(i < 32768) {
|
||||
if (i - 32768 > g711_ulaw_flipped_to_linear[j])
|
||||
j++;
|
||||
g711_linear_to_ulaw_flipped[(i - 32768) & 0xffff] = j;
|
||||
g711_linear_to_ulaw[(i - 32768) & 0xffff] = g711_flip[j];
|
||||
i++;
|
||||
}
|
||||
j = 255;
|
||||
while(i < 65536) {
|
||||
if (i - 32768 > g711_alaw_flipped_to_linear[j])
|
||||
j--;
|
||||
g711_linear_to_ulaw_flipped[(i - 32768) & 0xffff] = j;
|
||||
g711_linear_to_ulaw[(i - 32768) & 0xffff] = g711_flip[j];
|
||||
i++;
|
||||
}
|
||||
|
||||
/* transcode */
|
||||
for (i = 0; i < 256; i++) {
|
||||
g711_alaw_to_ulaw[i] = g711_linear_to_ulaw[(uint16_t)g711_alaw_to_linear[i]];
|
||||
g711_ulaw_to_alaw[i] = g711_linear_to_alaw[(uint16_t)g711_ulaw_to_linear[i]];
|
||||
g711_alaw_flipped_to_ulaw[i] = g711_linear_to_ulaw[(uint16_t)g711_alaw_to_linear[g711_flip[i]]];
|
||||
g711_ulaw_flipped_to_alaw[i] = g711_linear_to_alaw[(uint16_t)g711_ulaw_to_linear[g711_flip[i]]];
|
||||
g711_alaw_to_ulaw_flipped[i] = g711_flip[g711_linear_to_ulaw[(uint16_t)g711_alaw_to_linear[i]]];
|
||||
g711_ulaw_to_alaw_flipped[i] = g711_flip[g711_linear_to_alaw[(uint16_t)g711_ulaw_to_linear[i]]];
|
||||
}
|
||||
|
||||
g711_initialized = 1;
|
||||
}
|
||||
|
||||
void g711_encode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
int16_t *src = (int16_t *)src_data;
|
||||
uint8_t *dst;
|
||||
int len = src_len / 2, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_linear_to_alaw_flipped[(uint16_t)src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_encode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
int16_t *src = (int16_t *)src_data;
|
||||
uint8_t *dst;
|
||||
int len = src_len / 2, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_linear_to_ulaw_flipped[(uint16_t)src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_decode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data;
|
||||
int16_t *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len * 2);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_alaw_flipped_to_linear[src[i]];
|
||||
*dst_data = (uint8_t *)dst;
|
||||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
void g711_decode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data;
|
||||
int16_t *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len * 2);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_ulaw_flipped_to_linear[src[i]];
|
||||
*dst_data = (uint8_t *)dst;
|
||||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
void g711_encode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
int16_t *src = (int16_t *)src_data;
|
||||
uint8_t *dst;
|
||||
int len = src_len / 2, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_linear_to_alaw[(uint16_t)src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_encode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
int16_t *src = (int16_t *)src_data;
|
||||
uint8_t *dst;
|
||||
int len = src_len / 2, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_linear_to_ulaw[(uint16_t)src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_decode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data;
|
||||
int16_t *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len * 2);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_alaw_to_linear[src[i]];
|
||||
*dst_data = (uint8_t *)dst;
|
||||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
void g711_decode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data;
|
||||
int16_t *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len * 2);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_ulaw_to_linear[src[i]];
|
||||
*dst_data = (uint8_t *)dst;
|
||||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
void g711_transcode_alaw_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_alaw_to_ulaw[src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_alaw_flipped_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_alaw_flipped_to_ulaw[src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_alaw_to_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_alaw_to_ulaw_flipped[src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_ulaw_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_ulaw_to_alaw[src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_ulaw_flipped_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_ulaw_flipped_to_alaw[src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_ulaw_to_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_ulaw_to_alaw_flipped[src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
||||
void g711_transcode_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint8_t *src = src_data, *dst;
|
||||
int len = src_len, i;
|
||||
|
||||
if (!g711_initialized) {
|
||||
fprintf(stderr, "G711 codec not initialized! Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
dst = malloc(len);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = g711_flip[src[i]];
|
||||
*dst_data = dst;
|
||||
*dst_len = len;
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
void g711_init(void);
|
||||
void g711_encode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_encode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_decode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_decode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_encode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_encode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_decode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_decode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_alaw_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_alaw_flipped_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_alaw_to_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_ulaw_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_ulaw_flipped_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_ulaw_to_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void g711_transcode_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libgoertzel.a
|
||||
|
||||
libgoertzel_a_SOURCES = \
|
||||
goertzel.c
|
|
@ -1,120 +0,0 @@
|
|||
/* 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 "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "goertzel.h"
|
||||
|
||||
/*
|
||||
* audio level calculation
|
||||
*/
|
||||
|
||||
/* Return average value (rectified value)
|
||||
* The input must not have any dc offset!
|
||||
* For a perfect rectangualr wave, the result would equal the peak level.
|
||||
* For a sine wave the result would be factor (2 / PI) below peak level.
|
||||
*/
|
||||
double audio_mean_level(sample_t *samples, int length)
|
||||
{
|
||||
double level, sk;
|
||||
int n;
|
||||
|
||||
/* level calculation */
|
||||
level = 0;
|
||||
for (n = 0; n < length; n++) {
|
||||
sk = samples[n];
|
||||
if (sk < 0)
|
||||
level -= (double)sk;
|
||||
if (sk > 0)
|
||||
level += (double)sk;
|
||||
}
|
||||
level = level / (double)length;
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
/* use hamming window */
|
||||
double window[256];
|
||||
int window_generated = 0;
|
||||
|
||||
static void gen_window(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 256; i++)
|
||||
window[i] = 0.54 - 0.46 * cos(2.0 * M_PI * (double)i / 256.0);
|
||||
window_generated = 1;
|
||||
}
|
||||
|
||||
void audio_goertzel_init(goertzel_t *goertzel, double freq, int samplerate)
|
||||
{
|
||||
if (!window_generated)
|
||||
gen_window();
|
||||
memset(goertzel, 0, sizeof(*goertzel));
|
||||
goertzel->coeff = 2.0 * cos(2.0 * M_PI * freq / (double)samplerate);
|
||||
}
|
||||
|
||||
/*
|
||||
* goertzel filter
|
||||
*/
|
||||
|
||||
/* filter frequencies and return their levels
|
||||
*
|
||||
* samples: pointer to sample buffer
|
||||
* length: length of buffer: -7.4 dB @ +-(1 / duration) Hz and -INF @ +-(1 / duration * 2) Hz
|
||||
* -> if duration is 10 ms, we got -7.4 dB @ +-100 Hz and -INF at +-200 Hz
|
||||
* offset: for ring buffer, start here and wrap around to 0 when length has been hit
|
||||
* coeff: array of coefficients (coeff << 15)
|
||||
* result: array of result levels (peak value of the target frequency)
|
||||
* k: number of frequencies to check
|
||||
*/
|
||||
void audio_goertzel(goertzel_t *goertzel, sample_t *samples, int length, int offset, double *result, int k)
|
||||
{
|
||||
double sk, sk1, sk2;
|
||||
double cos2pik;
|
||||
int i, n;
|
||||
|
||||
/* we do goertzel */
|
||||
for (i = 0; i < k; i++) {
|
||||
sk = 0;
|
||||
sk1 = 0;
|
||||
sk2 = 0;
|
||||
cos2pik = goertzel[i].coeff;
|
||||
/* note: after 'length' cycles, offset is restored to its initial value */
|
||||
for (n = 0; n < length; n++) {
|
||||
sk = (cos2pik * sk1) - sk2 + samples[offset++] * window[n * 256 / length];
|
||||
sk2 = sk1;
|
||||
sk1 = sk;
|
||||
if (offset == length)
|
||||
offset = 0;
|
||||
}
|
||||
/* compute level of signal */
|
||||
result[i] = sqrt(
|
||||
(sk * sk) -
|
||||
(cos2pik * sk * sk2) +
|
||||
(sk2 * sk2)
|
||||
) / (double)length * 4 / 1.08;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
double audio_mean_level(sample_t *samples, int length);
|
||||
|
||||
typedef struct goertzel {
|
||||
double coeff;
|
||||
} goertzel_t;
|
||||
|
||||
void audio_goertzel_init(goertzel_t *goertzel, double freq, int samplerate);
|
||||
void audio_goertzel(goertzel_t *goertzel, sample_t *samples, int length, int offset, double *result, int k);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libhagelbarger.a
|
||||
|
||||
libhagelbarger_a_SOURCES = \
|
||||
hagelbarger.c
|
|
@ -1,101 +0,0 @@
|
|||
/* Hagelbarger (6,19) code
|
||||
*
|
||||
* A burst up to 6 encoded bits may be corrupt, to correct them.
|
||||
* After corrupt bits, a minimum of 19 bits must be correct to correct
|
||||
* another burst of corrupted bits.
|
||||
*
|
||||
* There is no parity check, so it is required to check all information
|
||||
* elements of each message. With NMT System: Messages that contain signals
|
||||
* or digits are protected by repeating the digits in the information element.
|
||||
*
|
||||
* (C) 2017 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"
|
||||
|
||||
/* enable to debug the process of parity check */
|
||||
//#define DEBUG_HAGEL
|
||||
|
||||
/* To encode NMT message: (MSB first)
|
||||
* Use input with 9 bytes, the last byte must be 0x00.
|
||||
* Use output with 18 bytes, ignore the last four (lower) bits of last byte.
|
||||
* Use length of 70.
|
||||
*/
|
||||
void hagelbarger_encode(const uint8_t *input, uint8_t *output, int length)
|
||||
{
|
||||
uint8_t reg = 0x00, data, check;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
/* get data from input (MSB first) */
|
||||
data = (input[i / 8] >> (7 - (i & 7))) & 1;
|
||||
/* push data into shift register (LSB first) */
|
||||
reg = (reg << 1) | data;
|
||||
/* get data bit from register */
|
||||
data = (reg >> 6) & 1;
|
||||
/* calc check bit from register */
|
||||
check = (reg + (reg >> 3) + 1) & 1;
|
||||
/* put check bit and data bit to output (MSB first) */
|
||||
output[i / 4] = (output[i / 4] << 2) | (check << 1) | data;
|
||||
}
|
||||
/* shift last output byte all the way to MSB */
|
||||
while ((i % 4))
|
||||
output[i++ / 4] <<= 2;
|
||||
}
|
||||
|
||||
/* To decode NMT message: (MSB first)
|
||||
* Use input with 19 bytes, the unused last 12 (lower) bits must be zero.
|
||||
* Use output with 8 bytes.
|
||||
* Use length of 64.
|
||||
*/
|
||||
void hagelbarger_decode(const uint8_t *input, uint8_t *output, int length)
|
||||
{
|
||||
uint16_t reg_data = 0x00, reg_check = 0xff, data, check, r_parity, s_parity;
|
||||
int i, o;
|
||||
|
||||
length += 10;
|
||||
|
||||
for (i = 0, o = 0; i < length; i++) {
|
||||
/* get check bit from input (MSB first) */
|
||||
check = (input[i / 4] >> (7 - (i & 3) * 2)) & 1;
|
||||
/* get data bit from input (MSB first) */
|
||||
data = (input[i / 4] >> (6 - (i & 3) * 2)) & 1;
|
||||
/* push check bit into shift register (LSB first) */
|
||||
reg_check = (reg_check << 1) | check;
|
||||
/* push data bit into shift register (LSB first) */
|
||||
reg_data = (reg_data << 1) | data;
|
||||
/* calculate parity */
|
||||
r_parity = (reg_data + (reg_data >> 3) + (reg_check >> 6) + 1) & 1;
|
||||
s_parity = ((reg_data >> 3) + (reg_data >> 6) + (reg_check >> 9) + 1) & 1;
|
||||
#ifdef DEBUG_HAGEL
|
||||
printf("#%d: r=%d s=%d\n", i - 10, r_parity, s_parity);
|
||||
#endif
|
||||
/* flip message bit, if both parity checks fail */
|
||||
/* use 4th bit that will be shifted to 5th bit next loop */
|
||||
if (r_parity && s_parity)
|
||||
reg_data ^= 0x0008;
|
||||
/* put message bit to output (MSB first) */
|
||||
if (i >= 10) {
|
||||
output[o / 8] = (output[o / 8] << 1) | ((reg_data >> 4) & 1);
|
||||
o++;
|
||||
}
|
||||
}
|
||||
/* shift last output byte all the way to MSB */
|
||||
while ((o % 8))
|
||||
output[o++ / 8] <<= 1;
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
void hagelbarger_encode(const uint8_t *input, uint8_t *output, int length);
|
||||
void hagelbarger_decode(const uint8_t *input, uint8_t *output, int length);
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) $(IMAGEMAGICK_CFLAGS)
|
||||
|
||||
noinst_LIBRARIES = libimage.a
|
||||
|
||||
libimage_a_SOURCES = \
|
||||
img.c
|
||||
|
||||
if HAVE_MAGICK
|
||||
AM_CPPFLAGS += -DHAVE_MAGICK
|
||||
endif
|
||||
|
|
@ -1,394 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "img.h"
|
||||
|
||||
int save_depth = 16;
|
||||
|
||||
#ifdef HAVE_MAGICK
|
||||
#include <MagickCore/MagickCore.h>
|
||||
|
||||
/* load given image to memory. return short RGB values */
|
||||
unsigned short *load_img(int *width, int *height, const char *filename, int index)
|
||||
{
|
||||
Image *image = NULL;
|
||||
ImageInfo *imageinfo = NULL;
|
||||
ExceptionInfo *exception;
|
||||
unsigned short *img = NULL;
|
||||
|
||||
MagickCoreGenesis(NULL, MagickFalse);
|
||||
// InitializeMagick(NULL);
|
||||
imageinfo = CloneImageInfo(0);
|
||||
exception = AcquireExceptionInfo();
|
||||
|
||||
sprintf(imageinfo->filename, filename, index);
|
||||
|
||||
image = ReadImage(imageinfo, exception);
|
||||
if (!image) {
|
||||
// printf("failed to read image '%s' via *magick\n", filename);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
*width = image->columns;
|
||||
*height = image->rows;
|
||||
|
||||
img = (unsigned short *)malloc((*width) * (*height) * 3 * 2);
|
||||
if (!img) {
|
||||
printf("%s:failed to allocate image data\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ExportImagePixels(image, 0, 0, *width, *height, "RGB", ShortPixel, img, NULL);
|
||||
// DispatchImage(image, 0, 0, *width, *height, "RGB", ShortPixel, img, NULL);
|
||||
|
||||
exit:
|
||||
if (image)
|
||||
DestroyImage(image);
|
||||
|
||||
if (imageinfo)
|
||||
DestroyImageInfo(imageinfo);
|
||||
|
||||
if (exception)
|
||||
DestroyExceptionInfo(exception);
|
||||
|
||||
MagickCoreTerminus();
|
||||
// DestroyMagick();
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
/* save given image */
|
||||
int save_img(unsigned short *img, int width, int height, int alpha, const char *filename, int index)
|
||||
{
|
||||
int rc = -1;
|
||||
Image *image = NULL;
|
||||
ImageInfo *imageinfo = NULL;
|
||||
ExceptionInfo *exception;
|
||||
|
||||
MagickCoreGenesis(NULL, MagickFalse);
|
||||
// InitializeMagick(NULL);
|
||||
imageinfo = CloneImageInfo(0);
|
||||
exception = AcquireExceptionInfo();
|
||||
|
||||
imageinfo->quality = 100;
|
||||
if (strlen(filename) >= 4 && !strcmp(filename + strlen(filename) - 4, ".png"))
|
||||
imageinfo->quality = 1;
|
||||
|
||||
image=ConstituteImage(width, height, (alpha)?"RGBA":"RGB", ShortPixel, img, exception);
|
||||
if (!image) {
|
||||
printf("%s:failed to prepare to write image\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* store as 16 bit, if lib and format supports it */
|
||||
image->depth = save_depth;
|
||||
|
||||
sprintf(image->filename, filename, index); /* ACHTUNG: nicht imageinfo!!! */
|
||||
if (!WriteImage(imageinfo, image, exception)) {
|
||||
printf("%s:failed to write image\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
|
||||
exit:
|
||||
if (image)
|
||||
DestroyImage(image);
|
||||
|
||||
if (imageinfo)
|
||||
DestroyImageInfo(imageinfo);
|
||||
|
||||
if (exception)
|
||||
DestroyExceptionInfo(exception);
|
||||
|
||||
MagickCoreTerminus();
|
||||
// DestroyMagick();
|
||||
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
|
||||
/* load given image to memory. return short RGB values */
|
||||
unsigned short *load_img(int *width, int *height, const char *filename, int index)
|
||||
{
|
||||
FILE *fp = NULL;
|
||||
unsigned short *img = NULL;
|
||||
char line[256];
|
||||
int words, i;
|
||||
|
||||
sprintf(line, filename, index);
|
||||
// printf("reading image: %s\n", line);
|
||||
fp = fopen(line, "r");
|
||||
if (!fp) {
|
||||
// printf("failed to read ppm image '%s'\n", filename);
|
||||
goto exit;
|
||||
}
|
||||
again1:
|
||||
if (!fgets(line, sizeof(line), fp)) {
|
||||
printf("%s:failed to read image depth\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
line[sizeof(line)-1] = '\0';
|
||||
if (line[0]) line[strlen(line)-1] = '\0';
|
||||
if (line[0] == '#')
|
||||
goto again1;
|
||||
if (!!strcmp(line, "P6")) {
|
||||
printf("%s:expecting image depth 'P6'\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
again2:
|
||||
if (!fgets(line, sizeof(line), fp)) {
|
||||
printf("%s:failed to read image size\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
line[sizeof(line)-1] = '\0';
|
||||
if (line[0]) line[strlen(line)-1] = '\0';
|
||||
if (line[0] == '#')
|
||||
goto again2;
|
||||
sscanf(line, "%d %d", width, height);
|
||||
// printf("Image size: w=%d h=%d\n", *width, *height);
|
||||
again3:
|
||||
if (!fgets(line, sizeof(line), fp)) {
|
||||
printf("%s:failed to read line '255' or '65535'\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
line[sizeof(line)-1] = '\0';
|
||||
if (line[0]) line[strlen(line)-1] = '\0';
|
||||
if (line[0] == '#')
|
||||
goto again3;
|
||||
if (!strcmp(line, "255")) {
|
||||
words = 1;
|
||||
} else
|
||||
if (!strcmp(line, "65535")) {
|
||||
words = 2;
|
||||
} else {
|
||||
printf("%s:expecting line '255' or '65535'\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
img = (unsigned short *)malloc((*width) * (*height) * 3 * 2);
|
||||
if (!img) {
|
||||
printf("%s:failed to allocate image data\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
if (fread(img, (*width) * (*height) * 3 * words, 1, fp) != 1) {
|
||||
printf("%s:failed to read image data\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* char to short (255 -> 65535) */
|
||||
if (words == 1) {
|
||||
unsigned char *from = (unsigned char *)img, c;
|
||||
for (i = (*width) * (*height) * 3 - 1; i >= 0; i--) {
|
||||
c = from[i];
|
||||
img[i] = (c << 8) | c;
|
||||
}
|
||||
} else {
|
||||
/* correct byte order */
|
||||
unsigned short v;
|
||||
unsigned char *from = (unsigned char *)img;
|
||||
for (i = 0; i < (*width) * (*height) * 3; i++) {
|
||||
v = ((*from++) << 8);
|
||||
v |= (*from++);
|
||||
img[i] = v;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
/* save given image */
|
||||
int save_img(unsigned short *img, int width, int height, int alpha, const char *filename, int index)
|
||||
{
|
||||
FILE *fp = NULL;
|
||||
int rc = -1;
|
||||
char line[256];
|
||||
int i;
|
||||
unsigned short v;
|
||||
unsigned char *to;
|
||||
|
||||
if (alpha) {
|
||||
printf("%s:cannot save alpha component with PPM support only\n", __func__);
|
||||
alpha = 0;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
sprintf(line, filename, index);
|
||||
// printf("writing image: %s\n", line);
|
||||
fp = fopen(line, "w");
|
||||
if (!fp) {
|
||||
printf("%s:failed to write image\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
fprintf(fp, "P6\n%d %d\n65535\n", width, height);
|
||||
|
||||
/* correct byte order, write and restore byte order */
|
||||
to = (unsigned char *)img;
|
||||
for (i = 0; i < width * height * 3; i++) {
|
||||
v = img[i];
|
||||
if (i/100*i == i) { printf("%04x ", v); }
|
||||
(*to++) = v >> 8;
|
||||
(*to++) = v;
|
||||
}
|
||||
rc = fwrite(img, width * height * 3 * 2, 1, fp);
|
||||
to = (unsigned char *)img;
|
||||
for (i = 0; i < width * height * 3; i++) {
|
||||
v = (*to++) << 8;
|
||||
v |= (*to++);
|
||||
img[i] = v;
|
||||
}
|
||||
if (rc != 1) {
|
||||
printf("%s:failed to write image data\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
|
||||
exit:
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
int save_img_array(double *array, int width, int height, int alpha, const char *filename, int index)
|
||||
{
|
||||
int rc = -1;
|
||||
unsigned short *img = NULL;
|
||||
int components;
|
||||
|
||||
#ifndef HAVE_MAGICK
|
||||
if (alpha) {
|
||||
printf("%s:warning, cannot save alpha component with PPM support only\n", __func__);
|
||||
alpha = 0;
|
||||
}
|
||||
#endif
|
||||
components = (alpha) ? 4 : 3;
|
||||
|
||||
img = (unsigned short *)malloc(width * height * components * 2);
|
||||
if (!img) {
|
||||
printf("%s:failed to allocate image data\n", __func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
array2img_short(array, width, height, img, width, height, alpha);
|
||||
|
||||
save_img(img, width, height, alpha, filename, index);
|
||||
|
||||
rc = 0;
|
||||
|
||||
exit:
|
||||
if (img)
|
||||
free(img);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* convert an image to a three dimensional array of double
|
||||
* the size is: width, height, 3
|
||||
*/
|
||||
void img2array_short(unsigned short *img, int iw, int ih, double *array, int aw, int ah)
|
||||
{
|
||||
int x, y;
|
||||
int channel;
|
||||
double r, g, b;
|
||||
|
||||
channel = aw * ah;
|
||||
|
||||
for (y = 0; y < ih; y++) {
|
||||
for (x = 0; x < iw; x++) {
|
||||
r = img[(x+iw*y)*3] / 65535.0F;
|
||||
g = img[(x+iw*y)*3+1] / 65535.0F;
|
||||
b = img[(x+iw*y)*3+2] / 65535.0F;
|
||||
array[x+aw*y] = r;
|
||||
array[x+aw*y+channel] = g;
|
||||
array[x+aw*y+channel+channel] = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* convert a three dimensional array of double to an image
|
||||
* the size is: width, height, 3
|
||||
*/
|
||||
void array2img_short(double *array, int aw, int ah, unsigned short *img, int iw, int ih, int alpha)
|
||||
{
|
||||
int x, y, c;
|
||||
int channel, components;
|
||||
double r, g, b, a;
|
||||
|
||||
channel = aw * ah;
|
||||
components = (alpha) ? 4 : 3;
|
||||
|
||||
for (y = 0; y < ih; y++) {
|
||||
for (x = 0; x < iw; x++) {
|
||||
r = array[x+aw*y];
|
||||
c = (r * 65535.0F + 0.5F);
|
||||
if (c < 0)
|
||||
c = 0;
|
||||
else if (c > 65535)
|
||||
c = 65535;
|
||||
img[(x+iw*y)*components] = c;
|
||||
g = array[x+aw*y+channel];
|
||||
c = (g * 65535.0F + 0.5F);
|
||||
if (c < 0)
|
||||
c = 0;
|
||||
else if (c > 65535)
|
||||
c = 65535;
|
||||
img[(x+iw*y)*components+1] = c;
|
||||
b = array[x+aw*y+channel+channel];
|
||||
c = (b * 65535.0F + 0.5F);
|
||||
if (c < 0)
|
||||
c = 0;
|
||||
else if (c > 65535)
|
||||
c = 65535;
|
||||
img[(x+iw*y)*components+2] = c;
|
||||
if (alpha) {
|
||||
a = array[x+aw*y+channel+channel+channel];
|
||||
c = (a * 65535.0F + 0.5F);
|
||||
if (c < 0)
|
||||
c = 0;
|
||||
else if (c > 65535)
|
||||
c = 65535;
|
||||
img[(x+iw*y)*components+3] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* scale down image in img_buffer by calculating average
|
||||
*/
|
||||
void scale_img(unsigned short *img, int width, int height, int scale)
|
||||
{
|
||||
int w, h, i, j, x, y;
|
||||
int r, g, b;
|
||||
|
||||
if (scale == 1)
|
||||
return;
|
||||
|
||||
w = width / scale;
|
||||
h = height / scale;
|
||||
|
||||
for (i = 0; i < h; i++) {
|
||||
for (j = 0; j < w; j++) {
|
||||
r = g = b = 0;
|
||||
for (y = 0; y < scale; y++) {
|
||||
for (x = 0; x < scale; x++) {
|
||||
r += img[((i*scale+y) * width + j*scale+x) * 3 + 0];
|
||||
g += img[((i*scale+y) * width + j*scale+x) * 3 + 1];
|
||||
b += img[((i*scale+y) * width + j*scale+x) * 3 + 2];
|
||||
}
|
||||
}
|
||||
img[(i * w + j)*3 + 0] = r / scale / scale;
|
||||
img[(i * w + j)*3 + 1] = g / scale / scale;
|
||||
img[(i * w + j)*3 + 2] = b / scale / scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
extern int save_depth;
|
||||
unsigned short *load_img(int *width, int *height, const char *filename, int index);
|
||||
int save_img(unsigned short *img, int width, int height, int alpha, const char *filename, int index);
|
||||
int save_img_array(double *array, int width, int height, int alpha, const char *filename, int index);
|
||||
void img2array_short(unsigned short *img, int iw, int ih, double *array, int aw, int ah);
|
||||
void array2img_short(double *array, int aw, int ah, unsigned short *img, int iw, int ih, int alpha);
|
||||
void scale_img(unsigned short *img, int width, int height, int scale);
|
|
@ -1,6 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libjitter.a
|
||||
|
||||
libjitter_a_SOURCES = \
|
||||
jitter.c
|
|
@ -1,125 +0,0 @@
|
|||
/* 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 "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "jitter.h"
|
||||
|
||||
/* create jitter buffer */
|
||||
int jitter_create(jitter_t *jitter, int length)
|
||||
{
|
||||
memset(jitter, 0, sizeof(*jitter));
|
||||
jitter->spl = malloc(length * sizeof(sample_t));
|
||||
if (!jitter->spl) {
|
||||
PDEBUG(DDSP, DEBUG_ERROR, "No memory for jitter buffer.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
jitter->len = length;
|
||||
|
||||
jitter_reset(jitter);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void jitter_reset(jitter_t *jitter)
|
||||
{
|
||||
memset(jitter->spl, 0, jitter->len * sizeof(sample_t));
|
||||
|
||||
/* put write pointer ahead by half of the buffer length */
|
||||
jitter->inptr = jitter->len / 2;
|
||||
}
|
||||
|
||||
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, sample_t *samples, int length)
|
||||
{
|
||||
sample_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, sample_t *samples, int length)
|
||||
{
|
||||
sample_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;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
|
||||
typedef struct jitter {
|
||||
sample_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_reset(jitter_t *jitter);
|
||||
void jitter_destroy(jitter_t *jitter);
|
||||
void jitter_save(jitter_t *jb, sample_t *samples, int length);
|
||||
void jitter_load(jitter_t *jb, sample_t *samples, int length);
|
||||
void jitter_clear(jitter_t *jb);
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libmobile.a
|
||||
|
||||
libmobile_a_SOURCES = \
|
||||
sender.c \
|
||||
call.c \
|
||||
console.c \
|
||||
testton.c \
|
||||
cause.c \
|
||||
main_mobile.c
|
||||
|
||||
if HAVE_ALSA
|
||||
AM_CPPFLAGS += -DHAVE_ALSA
|
||||
endif
|
||||
|
||||
if HAVE_SDR
|
||||
AM_CPPFLAGS += -DHAVE_SDR
|
||||
endif
|
||||
|
|
@ -1,942 +0,0 @@
|
|||
/* interface between mobile network/phone implementation and OsmoCC
|
||||
*
|
||||
* (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 <arpa/inet.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libtimer/timer.h"
|
||||
#include "../libosmocc/endpoint.h"
|
||||
#include "../libosmocc/helper.h"
|
||||
#include "../libg711/g711.h"
|
||||
#include "cause.h"
|
||||
#include "sender.h"
|
||||
#include "call.h"
|
||||
#include "main_mobile.h"
|
||||
#include "console.h"
|
||||
|
||||
#define DISC_TIMEOUT 30
|
||||
|
||||
//#define DEBUG_LEVEL
|
||||
|
||||
#ifdef DEBUG_LEVEL
|
||||
static double level_of(double *samples, int count)
|
||||
{
|
||||
double level = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (samples[i] > level)
|
||||
level = samples[i];
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int connect_on_setup; /* send patterns towards fixed network */
|
||||
static int release_on_disconnect; /* release towards mobile phone, if OSMO-CC call disconnects, don't send disconnect tone */
|
||||
|
||||
osmo_cc_endpoint_t endpoint, *ep;
|
||||
|
||||
void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint16_t *src = (uint16_t *)src_data, *dst;
|
||||
int len = src_len / 2, i;
|
||||
|
||||
dst = malloc(len * 2);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = htons(src[i]);
|
||||
*dst_data = (uint8_t *)dst;
|
||||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
|
||||
{
|
||||
uint16_t *src = (uint16_t *)src_data, *dst;
|
||||
int len = src_len / 2, i;
|
||||
|
||||
dst = malloc(len * 2);
|
||||
if (!dst)
|
||||
return;
|
||||
for (i = 0; i < len; i++)
|
||||
dst[i] = ntohs(src[i]);
|
||||
*dst_data = (uint8_t *)dst;
|
||||
*dst_len = len * 2;
|
||||
}
|
||||
|
||||
static struct osmo_cc_helper_audio_codecs codecs[] = {
|
||||
{ "L16", 8000, 1, encode_l16, decode_l16 },
|
||||
{ "PCMA", 8000, 1, g711_encode_alaw, g711_decode_alaw },
|
||||
{ "PCMU", 8000, 1, g711_encode_ulaw, g711_decode_ulaw },
|
||||
{ NULL, 0, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
/* stream patterns/announcements */
|
||||
int16_t *ringback_spl = NULL;
|
||||
int ringback_size = 0;
|
||||
int ringback_max = 0;
|
||||
int16_t *hangup_spl = NULL;
|
||||
int hangup_size = 0;
|
||||
int hangup_max = 0;
|
||||
int16_t *busy_spl = NULL;
|
||||
int busy_size = 0;
|
||||
int busy_max = 0;
|
||||
int16_t *noanswer_spl = NULL;
|
||||
int noanswer_size = 0;
|
||||
int noanswer_max = 0;
|
||||
int16_t *outoforder_spl = NULL;
|
||||
int outoforder_size = 0;
|
||||
int outoforder_max = 0;
|
||||
int16_t *invalidnumber_spl = NULL;
|
||||
int invalidnumber_size = 0;
|
||||
int invalidnumber_max = 0;
|
||||
int16_t *congestion_spl = NULL;
|
||||
int congestion_size = 0;
|
||||
int congestion_max = 0;
|
||||
int16_t *recall_spl = NULL;
|
||||
int recall_size = 0;
|
||||
int recall_max = 0;
|
||||
|
||||
enum audio_pattern {
|
||||
PATTERN_NONE = 0,
|
||||
PATTERN_TEST,
|
||||
PATTERN_RINGBACK,
|
||||
PATTERN_HANGUP,
|
||||
PATTERN_BUSY,
|
||||
PATTERN_NOANSWER,
|
||||
PATTERN_OUTOFORDER,
|
||||
PATTERN_INVALIDNUMBER,
|
||||
PATTERN_CONGESTION,
|
||||
PATTERN_RECALL,
|
||||
};
|
||||
|
||||
static void get_pattern(const int16_t **spl, int *size, int *max, enum audio_pattern pattern)
|
||||
{
|
||||
*spl = NULL;
|
||||
*size = 0;
|
||||
*max = 0;
|
||||
|
||||
switch (pattern) {
|
||||
case PATTERN_RINGBACK:
|
||||
no_recall:
|
||||
*spl = ringback_spl;
|
||||
*size = ringback_size;
|
||||
*max = ringback_max;
|
||||
break;
|
||||
case PATTERN_HANGUP:
|
||||
if (!hangup_spl)
|
||||
goto no_hangup;
|
||||
*spl = hangup_spl;
|
||||
*size = hangup_size;
|
||||
*max = hangup_max;
|
||||
break;
|
||||
case PATTERN_BUSY:
|
||||
no_hangup:
|
||||
no_noanswer:
|
||||
*spl = busy_spl;
|
||||
*size = busy_size;
|
||||
*max = busy_max;
|
||||
break;
|
||||
case PATTERN_NOANSWER:
|
||||
if (!noanswer_spl)
|
||||
goto no_noanswer;
|
||||
*spl = noanswer_spl;
|
||||
*size = noanswer_size;
|
||||
*max = noanswer_max;
|
||||
break;
|
||||
case PATTERN_OUTOFORDER:
|
||||
if (!outoforder_spl)
|
||||
goto no_outoforder;
|
||||
*spl = outoforder_spl;
|
||||
*size = outoforder_size;
|
||||
*max = outoforder_max;
|
||||
break;
|
||||
case PATTERN_INVALIDNUMBER:
|
||||
if (!invalidnumber_spl)
|
||||
goto no_invalidnumber;
|
||||
*spl = invalidnumber_spl;
|
||||
*size = invalidnumber_size;
|
||||
*max = invalidnumber_max;
|
||||
break;
|
||||
case PATTERN_CONGESTION:
|
||||
no_outoforder:
|
||||
no_invalidnumber:
|
||||
*spl = congestion_spl;
|
||||
*size = congestion_size;
|
||||
*max = congestion_max;
|
||||
break;
|
||||
case PATTERN_RECALL:
|
||||
if (!recall_spl)
|
||||
goto no_recall;
|
||||
*spl = recall_spl;
|
||||
*size = recall_size;
|
||||
*max = recall_max;
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
static enum audio_pattern cause2pattern(int cause)
|
||||
{
|
||||
int pattern;
|
||||
|
||||
switch (cause) {
|
||||
case CAUSE_NORMAL:
|
||||
pattern = PATTERN_HANGUP;
|
||||
break;
|
||||
case CAUSE_BUSY:
|
||||
pattern = PATTERN_BUSY;
|
||||
break;
|
||||
case CAUSE_NOANSWER:
|
||||
pattern = PATTERN_NOANSWER;
|
||||
break;
|
||||
case CAUSE_OUTOFORDER:
|
||||
pattern = PATTERN_OUTOFORDER;
|
||||
break;
|
||||
case CAUSE_INVALNUMBER:
|
||||
pattern = PATTERN_INVALIDNUMBER;
|
||||
break;
|
||||
case CAUSE_NOCHANNEL:
|
||||
pattern = PATTERN_CONGESTION;
|
||||
break;
|
||||
default:
|
||||
pattern = PATTERN_HANGUP;
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
enum process_state {
|
||||
PROCESS_IDLE = 0, /* IDLE */
|
||||
PROCESS_SETUP_RO, /* call from radio to OSMO-CC */
|
||||
PROCESS_SETUP_RT, /* call from OSMO-CC to radio */
|
||||
PROCESS_ALERTING_RO, /* call from radio to OSMO-CC */
|
||||
PROCESS_ALERTING_RT, /* call from OSMO-CC to radio */
|
||||
PROCESS_CONNECT,
|
||||
PROCESS_DISCONNECT,
|
||||
};
|
||||
|
||||
/* call process */
|
||||
typedef struct process {
|
||||
struct process *next;
|
||||
int callref;
|
||||
enum process_state state;
|
||||
int audio_disconnected; /* if not associated with transceiver anymore */
|
||||
enum audio_pattern pattern;
|
||||
int audio_pos;
|
||||
uint8_t cause;
|
||||
struct timer timer;
|
||||
osmo_cc_session_t *session;
|
||||
osmo_cc_session_codec_t *codec; /* codec to send */
|
||||
} process_t;
|
||||
|
||||
static process_t *process_head = NULL;
|
||||
|
||||
static void process_timeout(struct timer *timer);
|
||||
static void indicate_disconnect_release(int callref, int cause, uint8_t msg_type);
|
||||
|
||||
static process_t *create_process(int callref, enum process_state state)
|
||||
{
|
||||
process_t *process;
|
||||
|
||||
process = calloc(sizeof(*process), 1);
|
||||
if (!process) {
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "No memory!\n");
|
||||
abort();
|
||||
}
|
||||
timer_init(&process->timer, process_timeout, process);
|
||||
process->next = process_head;
|
||||
process_head = process;
|
||||
|
||||
process->callref = callref;
|
||||
process->state = state;
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
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;
|
||||
timer_exit(&process->timer);
|
||||
if (process->session)
|
||||
osmo_cc_free_session(process->session);
|
||||
free(process);
|
||||
return;
|
||||
}
|
||||
process_p = &process->next;
|
||||
process = process->next;
|
||||
}
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref %d not found!\n", callref);
|
||||
}
|
||||
|
||||
static process_t *get_process(int callref)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
|
||||
while (process) {
|
||||
if (process->callref == callref)
|
||||
return process;
|
||||
process = process->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void new_state_process(int callref, enum process_state state)
|
||||
{
|
||||
process_t *process = get_process(callref);
|
||||
|
||||
if (!process) {
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref %d not found!\n", callref);
|
||||
return;
|
||||
}
|
||||
PDEBUG(DCALL, DEBUG_DEBUG, "Changing state for callref %d %d->%d\n", callref, process->state, state);
|
||||
process->state = state;
|
||||
}
|
||||
|
||||
static void set_pattern_process(int callref, enum audio_pattern pattern)
|
||||
{
|
||||
process_t *process = get_process(callref);
|
||||
|
||||
if (!process) {
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref %d not found!\n", callref);
|
||||
return;
|
||||
}
|
||||
process->pattern = pattern;
|
||||
process->audio_pos = 0;
|
||||
}
|
||||
|
||||
/* disconnect audio, now send audio directly from pattern/announcement, not from transceiver */
|
||||
static void disconnect_process(int callref, int cause)
|
||||
{
|
||||
process_t *process = get_process(callref);
|
||||
|
||||
if (!process) {
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref %d not found!\n", callref);
|
||||
return;
|
||||
}
|
||||
process->pattern = cause2pattern(cause);
|
||||
process->audio_disconnected = 1;
|
||||
process->audio_pos = 0;
|
||||
process->cause = cause;
|
||||
timer_start(&process->timer, DISC_TIMEOUT);
|
||||
}
|
||||
|
||||
static void get_process_patterns(process_t *process, int16_t *samples, int length)
|
||||
{
|
||||
const int16_t *spl;
|
||||
int size, max, pos;
|
||||
|
||||
get_pattern(&spl, &size, &max, process->pattern);
|
||||
|
||||
/* stream sample */
|
||||
pos = process->audio_pos;
|
||||
while(length--) {
|
||||
if (pos >= size)
|
||||
*samples++ = 0;
|
||||
else
|
||||
*samples++ = spl[pos] >> 2;
|
||||
if (++pos == max)
|
||||
pos = 0;
|
||||
}
|
||||
process->audio_pos = pos;
|
||||
}
|
||||
|
||||
static void process_timeout(struct timer *timer)
|
||||
{
|
||||
process_t *process = (process_t *)timer->priv;
|
||||
|
||||
{
|
||||
/* announcement timeout */
|
||||
if (process->state == PROCESS_DISCONNECT) {
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call released toward mobile network (after timeout)\n");
|
||||
call_down_release(process->callref, process->cause);
|
||||
}
|
||||
indicate_disconnect_release(process->callref, process->cause, OSMO_CC_MSG_REL_IND);
|
||||
destroy_process(process->callref);
|
||||
}
|
||||
}
|
||||
|
||||
void down_audio(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len)
|
||||
{
|
||||
process_t *process = codec->media->session->priv;
|
||||
sample_t samples[len / 2];
|
||||
|
||||
/* if we are disconnected, ignore audio */
|
||||
if (!process || process->pattern != PATTERN_NONE)
|
||||
return;
|
||||
int16_to_samples(samples, (int16_t *)data, len / 2);
|
||||
#ifdef DEBUG_LEVEL
|
||||
double lev = level_of(samples, len / 2);
|
||||
printf("festnetz-level: %s %.4f\n", debug_db(lev), (20 * log10(lev)));
|
||||
#endif
|
||||
call_down_audio(process->callref, samples, len / 2);
|
||||
}
|
||||
|
||||
static void indicate_setup(process_t *process, const char *callerid, const char *dialing, uint8_t network_type, const char *network_id)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND);
|
||||
/* network type */
|
||||
if (network_type)
|
||||
osmo_cc_add_ie_calling_network(msg, network_type, network_id);
|
||||
/* calling number */
|
||||
if (callerid && callerid[0])
|
||||
osmo_cc_add_ie_calling(msg, OSMO_CC_TYPE_SUBSCRIBER, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_ALLOWED, OSMO_CC_SCREEN_NETWORK, callerid);
|
||||
/* called number */
|
||||
if (dialing && dialing[0])
|
||||
osmo_cc_add_ie_called(msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, dialing);
|
||||
/* bearer capability */
|
||||
osmo_cc_add_ie_bearer(msg, OSMO_CC_CODING_ITU_T, OSMO_CC_CAPABILITY_AUDIO, OSMO_CC_MODE_CIRCUIT);
|
||||
/* sdp offer */
|
||||
process->session = osmo_cc_helper_audio_offer(&ep->session_config, process, codecs, down_audio, msg, 1);
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Indicate OSMO-CC setup towards fixed network\n");
|
||||
osmo_cc_ll_msg(ep, process->callref, msg);
|
||||
}
|
||||
|
||||
static void indicate_proceeding(int callref, const char *sdp)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_PROC_IND);
|
||||
|
||||
/* sdp */
|
||||
osmo_cc_add_ie_sdp(msg, sdp);
|
||||
|
||||
/* progress information */
|
||||
osmo_cc_add_ie_progress(msg, OSMO_CC_CODING_ITU_T, OSMO_CC_LOCATION_BEYOND_INTERWORKING, OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE);
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Indicate OSMO-CC call confirm towards fixed network\n");
|
||||
osmo_cc_ll_msg(ep, callref, msg);
|
||||
}
|
||||
|
||||
static void indicate_alerting(int callref)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND);
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Indicate OSMO-CC alerting towards fixed network\n");
|
||||
osmo_cc_ll_msg(ep, callref, msg);
|
||||
}
|
||||
|
||||
static void indicate_answer(int callref, const char *sdp, const char *connectid)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_CNF);
|
||||
/* calling number */
|
||||
if (connectid && connectid[0])
|
||||
osmo_cc_add_ie_calling(msg, OSMO_CC_TYPE_SUBSCRIBER, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_ALLOWED, OSMO_CC_SCREEN_NETWORK, connectid);
|
||||
|
||||
/* sdp */
|
||||
if (sdp)
|
||||
osmo_cc_add_ie_sdp(msg, sdp);
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Indicate OSMO-CC answer towards fixed network\n");
|
||||
osmo_cc_ll_msg(ep, callref, msg);
|
||||
}
|
||||
|
||||
static void indicate_answer_ack(int callref)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_COMP_IND);
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Indicate OSMO-CC setup complete towards fixed network\n");
|
||||
osmo_cc_ll_msg(ep, callref, msg);
|
||||
}
|
||||
|
||||
static void indicate_disconnect_release(int callref, int cause, uint8_t msg_type)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
msg = osmo_cc_new_msg(msg_type);
|
||||
|
||||
/* cause */
|
||||
osmo_cc_add_ie_cause(msg, OSMO_CC_LOCATION_PRIV_SERV_LOC_USER, cause, 0, 0);
|
||||
|
||||
/* progress information */
|
||||
if (msg_type == OSMO_CC_MSG_DISC_IND)
|
||||
osmo_cc_add_ie_progress(msg, OSMO_CC_CODING_ITU_T, OSMO_CC_LOCATION_BEYOND_INTERWORKING, OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE);
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "%s OSMO-CC %s towards fixed network\n", (msg_type == OSMO_CC_MSG_REL_CNF) ? "Confirm" : "Indicated", (msg_type == OSMO_CC_MSG_DISC_IND) ? "disconnect" : "release");
|
||||
osmo_cc_ll_msg(ep, callref, msg);
|
||||
}
|
||||
|
||||
/* Setup is received from transceiver. */
|
||||
int call_up_setup(const char *callerid, const char *dialing, uint8_t network, const char *network_id)
|
||||
{
|
||||
osmo_cc_call_t *call;
|
||||
process_t *process;
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Incoming call from '%s' to '%s'\n", callerid ? : "unknown", dialing);
|
||||
if (!strcmp(dialing, "010"))
|
||||
PDEBUG(DCALL, DEBUG_INFO, " -> Call to Operator '%s'\n", dialing);
|
||||
|
||||
call = osmo_cc_call_new(ep);
|
||||
|
||||
process = create_process(call->callref, PROCESS_SETUP_RO);
|
||||
|
||||
indicate_setup(process, callerid, dialing, network, network_id);
|
||||
|
||||
return call->callref;
|
||||
}
|
||||
|
||||
/* Transceiver indicates alerting. */
|
||||
void call_up_alerting(int callref)
|
||||
{
|
||||
if (!callref) {
|
||||
PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring alerting, because callref not set. (not for us)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call is alerting\n");
|
||||
|
||||
if (!connect_on_setup)
|
||||
indicate_alerting(callref);
|
||||
set_pattern_process(callref, PATTERN_RINGBACK);
|
||||
new_state_process(callref, PROCESS_ALERTING_RT);
|
||||
}
|
||||
|
||||
/* Transceiver indicates early audio */
|
||||
void call_up_early(int callref)
|
||||
{
|
||||
set_pattern_process(callref, PATTERN_NONE);
|
||||
}
|
||||
|
||||
/* Transceiver indicates answer. */
|
||||
void call_up_answer(int callref, const char *connect_id)
|
||||
{
|
||||
if (!callref) {
|
||||
PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring answer, because callref not set. (not for us)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call has been answered by '%s'\n", connect_id);
|
||||
|
||||
if (!connect_on_setup)
|
||||
indicate_answer(callref, NULL, connect_id);
|
||||
set_pattern_process(callref, PATTERN_NONE);
|
||||
new_state_process(callref, PROCESS_CONNECT);
|
||||
}
|
||||
|
||||
/* Transceiver indicates release. */
|
||||
void call_up_release(int callref, int cause)
|
||||
{
|
||||
process_t *process;
|
||||
|
||||
if (!callref) {
|
||||
PDEBUG(DCALL, DEBUG_DEBUG, "Ignoring release, because callref not set. (not for us)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call has been released with cause=%d\n", cause);
|
||||
|
||||
process = get_process(callref);
|
||||
if (process) {
|
||||
/* just keep OSMO-CC connection if tones shall be sent.
|
||||
* no tones while setting up / alerting the call. */
|
||||
if (connect_on_setup
|
||||
&& process->state != PROCESS_SETUP_RO
|
||||
&& process->state != PROCESS_ALERTING_RO)
|
||||
disconnect_process(callref, cause);
|
||||
else
|
||||
/* if no tones shall be sent, release on disconnect
|
||||
* or RO setup states */
|
||||
if (process->state == PROCESS_DISCONNECT
|
||||
|| process->state == PROCESS_SETUP_RO
|
||||
|| process->state == PROCESS_ALERTING_RO) {
|
||||
destroy_process(callref);
|
||||
indicate_disconnect_release(callref, cause, OSMO_CC_MSG_REL_IND);
|
||||
/* if no tones shall be sent, disconnect on all other states */
|
||||
} else {
|
||||
disconnect_process(callref, cause);
|
||||
indicate_disconnect_release(callref, cause, OSMO_CC_MSG_DISC_IND);
|
||||
}
|
||||
} else {
|
||||
/* we don't know about the process, just send release to upper layer anyway */
|
||||
indicate_disconnect_release(callref, cause, OSMO_CC_MSG_REL_IND);
|
||||
}
|
||||
}
|
||||
|
||||
/* turn recall tone on or off */
|
||||
void call_tone_recall(int callref, int on)
|
||||
{
|
||||
set_pattern_process(callref, (on) ? PATTERN_RECALL : PATTERN_NONE);
|
||||
}
|
||||
|
||||
/* forward audio to OSMO-CC or call instance */
|
||||
void call_up_audio(int callref, sample_t *samples, int count)
|
||||
{
|
||||
process_t *process;
|
||||
int16_t data[count];
|
||||
|
||||
if (count != 160) {
|
||||
fprintf(stderr, "Samples must be 160, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
if (!callref)
|
||||
return;
|
||||
|
||||
/* if we are disconnected, ignore audio */
|
||||
process = get_process(callref);
|
||||
if (!process || process->pattern != PATTERN_NONE)
|
||||
return;
|
||||
|
||||
/* forward audio */
|
||||
#ifdef DEBUG_LEVEL
|
||||
double lev = level_of(samples, count);
|
||||
printf(" mobil-level: %s%.4f\n", debug_db(lev), (20 * log10(lev)));
|
||||
#endif
|
||||
samples_to_int16(data, samples, count);
|
||||
osmo_cc_rtp_send(process->codec, (uint8_t *)data, count * 2, 1, count);
|
||||
/* don't destroy process here in case of an error */
|
||||
}
|
||||
|
||||
/* clock that is used to transmit patterns */
|
||||
void call_clock(void)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
|
||||
call_down_clock();
|
||||
|
||||
while(process) {
|
||||
if (process->pattern != PATTERN_NONE) {
|
||||
int16_t data[160];
|
||||
/* try to get patterns, else copy the samples we got */
|
||||
get_process_patterns(process, data, 160);
|
||||
#ifdef DEBUG_LEVEL
|
||||
sample_t samples[160];
|
||||
int16_to_samples(samples, (int16_t *)data->data, 160);
|
||||
double lev = level_of(samples, 160);
|
||||
printf(" mobil-level: %s%.4f\n", debug_db(lev), (20 * log10(lev)));
|
||||
samples_to_int16(data, samples, 160);
|
||||
#endif
|
||||
osmo_cc_rtp_send(process->codec, (uint8_t *)data, 160 * 2, 1, 160);
|
||||
/* don't destroy process here in case of an error */
|
||||
}
|
||||
process = process->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* messages received from fixed network */
|
||||
void ll_msg_cb(osmo_cc_endpoint_t __attribute__((unused)) *ep, uint32_t callref, osmo_cc_msg_t *msg)
|
||||
{
|
||||
process_t *process;
|
||||
uint8_t coding, location, progress, isdn_cause, socket_cause;
|
||||
uint16_t sip_cause;
|
||||
uint8_t type, plan, present, screen, caller_type;
|
||||
char caller_id[33], number[33];
|
||||
const char *suffix, *invalid;
|
||||
int rc;
|
||||
|
||||
process = get_process(callref);
|
||||
if (!process) {
|
||||
if (msg->type == OSMO_CC_MSG_SETUP_REQ)
|
||||
process = create_process(callref, PROCESS_SETUP_RT);
|
||||
else {
|
||||
/* release collisions is not forbidden */
|
||||
if (msg->type != OSMO_CC_MSG_REL_REQ)
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "No process!\n");
|
||||
osmo_cc_free_msg(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (process->audio_disconnected) {
|
||||
switch(msg->type) {
|
||||
case OSMO_CC_MSG_DISC_REQ:
|
||||
rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
||||
if (rc < 0)
|
||||
isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR;
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC disconnect from fixed network with cause %d\n", isdn_cause);
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call disconnected, releasing!\n");
|
||||
destroy_process(callref);
|
||||
indicate_disconnect_release(callref, isdn_cause, OSMO_CC_MSG_REL_IND);
|
||||
break;
|
||||
case OSMO_CC_MSG_REL_REQ:
|
||||
rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
||||
if (rc < 0)
|
||||
isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR;
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC release from fixed network with cause %d\n", isdn_cause);
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call released\n");
|
||||
destroy_process(callref);
|
||||
indicate_disconnect_release(callref, isdn_cause, OSMO_CC_MSG_REL_CNF);
|
||||
break;
|
||||
}
|
||||
osmo_cc_free_msg(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(msg->type) {
|
||||
case OSMO_CC_MSG_SETUP_REQ:
|
||||
{
|
||||
const char *sdp;
|
||||
|
||||
/* sdp accept */
|
||||
sdp = osmo_cc_helper_audio_accept(&ep->session_config, process, codecs, down_audio, msg, &process->session, &process->codec, 0);
|
||||
if (!sdp) {
|
||||
disconnect_process(callref, 47);
|
||||
indicate_disconnect_release(callref, 47, OSMO_CC_MSG_REJ_IND);
|
||||
break;
|
||||
}
|
||||
|
||||
/* caller id */
|
||||
rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, caller_id, sizeof(caller_id));
|
||||
if (rc < 0) {
|
||||
caller_type = TYPE_NOTAVAIL;
|
||||
caller_id[0] = '\0';
|
||||
} else {
|
||||
switch (type) {
|
||||
case OSMO_CC_TYPE_INTERNATIONAL:
|
||||
caller_type = TYPE_INTERNATIONAL;
|
||||
break;
|
||||
case OSMO_CC_TYPE_NATIONAL:
|
||||
caller_type = TYPE_NATIONAL;
|
||||
break;
|
||||
case OSMO_CC_TYPE_SUBSCRIBER:
|
||||
caller_type = TYPE_SUBSCRIBER;
|
||||
break;
|
||||
default:
|
||||
caller_type = TYPE_UNKNOWN;
|
||||
}
|
||||
if (present == OSMO_CC_PRESENT_RESTRICTED)
|
||||
caller_type = TYPE_ANONYMOUS;
|
||||
}
|
||||
|
||||
/* dialing */
|
||||
rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, number, sizeof(number));
|
||||
if (rc < 0)
|
||||
number[0] = '\0';
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC call from fixed network '%s' to mobile '%s'\n", caller_id, number);
|
||||
if (!connect_on_setup)
|
||||
indicate_proceeding(callref, sdp);
|
||||
else {
|
||||
PDEBUG(DCALL, DEBUG_DEBUG, "Early connecting after setup\n");
|
||||
indicate_answer(callref, sdp, number);
|
||||
}
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Outgoing call from '%s' to '%s'\n", caller_id, number);
|
||||
|
||||
/* insert '+' for international dialing */
|
||||
if (type == OSMO_CC_TYPE_INTERNATIONAL && number[0] != '+') {
|
||||
memmove(number + 1, number, sizeof(number) - 2);
|
||||
number[0] = '+';
|
||||
}
|
||||
|
||||
/* remove prefix, if any */
|
||||
suffix = mobile_number_remove_prefix(number);
|
||||
|
||||
/* check suffix length */
|
||||
invalid = mobile_number_check_length(suffix);
|
||||
if (invalid) {
|
||||
PDEBUG(DCALL, DEBUG_NOTICE, "Mobile number '%s' has invalid length: %s\n", suffix, invalid);
|
||||
disconnect_process(callref, OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT);
|
||||
if (!connect_on_setup) {
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Disconnecting OSMO-CC call towards fixed network (cause=%d)\n", OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT);
|
||||
indicate_disconnect_release(callref, OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT, OSMO_CC_MSG_DISC_IND);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* check suffix digits */
|
||||
invalid = mobile_number_check_digits(suffix);
|
||||
if (invalid) {
|
||||
PDEBUG(DCALL, DEBUG_NOTICE, "Mobile number '%s' has invalid digit: %s.\n", suffix, invalid);
|
||||
disconnect_process(callref, OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT);
|
||||
if (!connect_on_setup) {
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Disconnecting OSMO-CC call towards fixed network (cause=%d)\n", OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT);
|
||||
indicate_disconnect_release(callref, OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT, OSMO_CC_MSG_DISC_IND);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* check if suffix is valid */
|
||||
if (mobile_number_check_valid) {
|
||||
invalid = mobile_number_check_valid(suffix);
|
||||
if (invalid) {
|
||||
PDEBUG(DCALL, DEBUG_NOTICE, "Mobile number '%s' is invalid for this network: %s\n", suffix, invalid);
|
||||
disconnect_process(callref, OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT);
|
||||
if (!connect_on_setup) {
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Disconnecting OSMO-CC call towards fixed network (cause=%d)\n", OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT);
|
||||
indicate_disconnect_release(callref, OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT, OSMO_CC_MSG_DISC_IND);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* setup call */
|
||||
rc = call_down_setup(callref, caller_id, caller_type, suffix);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DCALL, DEBUG_NOTICE, "Call rejected, cause %d\n", -rc);
|
||||
if (!connect_on_setup) {
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Disconnecting OSMO-CC call towards fixed network (cause=%d)\n", -rc);
|
||||
indicate_disconnect_release(callref, -rc, OSMO_CC_MSG_DISC_IND);
|
||||
}
|
||||
disconnect_process(callref, -rc);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OSMO_CC_MSG_SETUP_ACK_REQ:
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC overlap from fixed network\n");
|
||||
rc = osmo_cc_helper_audio_negotiate(msg, &process->session, &process->codec);
|
||||
if (rc < 0) {
|
||||
nego_failed:
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Releasing, because codec negotiation failed.\n");
|
||||
destroy_process(callref);
|
||||
indicate_disconnect_release(callref, 47, OSMO_CC_MSG_REL_IND);
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call released toward mobile network\n");
|
||||
call_down_release(callref, 47);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case OSMO_CC_MSG_PROC_REQ:
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC proceeding from fixed network\n");
|
||||
rc = osmo_cc_helper_audio_negotiate(msg, &process->session, &process->codec);
|
||||
if (rc < 0)
|
||||
goto nego_failed;
|
||||
break;
|
||||
case OSMO_CC_MSG_PROGRESS_REQ:
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC progress from fixed network\n");
|
||||
rc = osmo_cc_helper_audio_negotiate(msg, &process->session, &process->codec);
|
||||
if (rc < 0)
|
||||
goto nego_failed;
|
||||
break;
|
||||
case OSMO_CC_MSG_ALERT_REQ:
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC alerting from fixed network\n");
|
||||
rc = osmo_cc_helper_audio_negotiate(msg, &process->session, &process->codec);
|
||||
if (rc < 0)
|
||||
goto nego_failed;
|
||||
new_state_process(callref, PROCESS_ALERTING_RO);
|
||||
break;
|
||||
case OSMO_CC_MSG_SETUP_RSP:
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC answer from fixed network\n");
|
||||
rc = osmo_cc_helper_audio_negotiate(msg, &process->session, &process->codec);
|
||||
if (rc < 0)
|
||||
goto nego_failed;
|
||||
new_state_process(callref, PROCESS_CONNECT);
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call answered\n");
|
||||
call_down_answer(callref);
|
||||
indicate_answer_ack(callref);
|
||||
break;
|
||||
case OSMO_CC_MSG_DISC_REQ:
|
||||
rc = osmo_cc_helper_audio_negotiate(msg, &process->session, &process->codec);
|
||||
if (rc < 0)
|
||||
goto nego_failed;
|
||||
rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
||||
if (rc < 0)
|
||||
isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR;
|
||||
rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress);
|
||||
if (rc < 0)
|
||||
progress = 0;
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC disconnect from fixed network with cause %d\n", isdn_cause);
|
||||
if (release_on_disconnect || (progress != 1 && progress != 8)) {
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Releasing, because we don't send disconnect tones to mobile phone\n");
|
||||
destroy_process(callref);
|
||||
indicate_disconnect_release(callref, isdn_cause, OSMO_CC_MSG_REL_IND);
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call released toward mobile network\n");
|
||||
call_down_release(callref, isdn_cause);
|
||||
break;
|
||||
}
|
||||
new_state_process(callref, PROCESS_DISCONNECT);
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call disconnected\n");
|
||||
call_down_disconnect(callref, isdn_cause);
|
||||
/* we might get released during disconnect handling!!! */
|
||||
process = get_process(callref);
|
||||
if (process && process->state == PROCESS_DISCONNECT)
|
||||
timer_start(&process->timer, DISC_TIMEOUT);
|
||||
break;
|
||||
case OSMO_CC_MSG_REJ_REQ:
|
||||
case OSMO_CC_MSG_REL_REQ:
|
||||
rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
||||
if (rc < 0)
|
||||
isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR;
|
||||
destroy_process(callref);
|
||||
if (msg->type == OSMO_CC_MSG_REL_REQ) {
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC release from fixed network with cause %d\n", isdn_cause);
|
||||
indicate_disconnect_release(callref, isdn_cause, OSMO_CC_MSG_REL_CNF);
|
||||
} else
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Received OSMO-CC reject from fixed network with cause %d\n", isdn_cause);
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call released toward mobile network\n");
|
||||
call_down_release(callref, isdn_cause);
|
||||
break;
|
||||
}
|
||||
osmo_cc_free_msg(msg);
|
||||
}
|
||||
|
||||
int call_init(const char *name, int _send_patterns, int _release_on_disconnect, int use_socket, int argc, const char *argv[])
|
||||
{
|
||||
int rc;
|
||||
|
||||
connect_on_setup = _send_patterns;
|
||||
release_on_disconnect = _release_on_disconnect;
|
||||
|
||||
g711_init();
|
||||
|
||||
ep = &endpoint;
|
||||
rc = osmo_cc_new(ep, OSMO_CC_VERSION, name, OSMO_CC_LOCATION_PRIV_SERV_LOC_USER, ll_msg_cb, (use_socket) ? NULL : console_msg, NULL, argc, argv);
|
||||
if (rc > 0)
|
||||
return -EINVAL;
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void call_exit(void)
|
||||
{
|
||||
if (ep) {
|
||||
osmo_cc_delete(ep);
|
||||
ep = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int call_handle(void)
|
||||
{
|
||||
return osmo_cc_handle();
|
||||
}
|
||||
|
||||
void call_media_handle(void)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
|
||||
while(process) {
|
||||
if (process->session)
|
||||
osmo_cc_session_handle(process->session);
|
||||
process = process->next;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
|
||||
/* number type, includes presentation info */
|
||||
enum number_type {
|
||||
TYPE_NOTAVAIL,
|
||||
TYPE_ANONYMOUS,
|
||||
TYPE_UNKNOWN,
|
||||
TYPE_SUBSCRIBER,
|
||||
TYPE_NATIONAL,
|
||||
TYPE_INTERNATIONAL,
|
||||
};
|
||||
|
||||
int call_init(const char *name, int _send_patterns, int _release_on_disconnect, int use_socket, int argc, const char *argv[]);
|
||||
void call_exit(void);
|
||||
int call_handle(void);
|
||||
void call_media_handle(void);
|
||||
|
||||
/* function pointer to delive MNCC messages to upper layer */
|
||||
extern int (*mncc_up)(uint8_t *buf, int length);
|
||||
/* MNCC messages from upper layer */
|
||||
void mncc_down(uint8_t *buf, int length);
|
||||
/* flush all calls in case of MNCC socket failure */
|
||||
void mncc_flush(void);
|
||||
|
||||
/* received messages */
|
||||
int call_up_setup(const char *callerid, const char *dialing, uint8_t network, const char *network_id);
|
||||
void call_up_alerting(int callref);
|
||||
void call_up_early(int callref);
|
||||
void call_up_answer(int callref, const char *connect_id);
|
||||
void call_up_release(int callref, int cause);
|
||||
void call_tone_recall(int callref, int on);
|
||||
|
||||
/* send messages */
|
||||
int call_down_setup(int callref, const char *caller_id, enum number_type caller_type, const char *dialing);
|
||||
void call_down_answer(int callref);
|
||||
void call_down_disconnect(int callref, int cause);
|
||||
void call_down_release(int callref, int cause);
|
||||
|
||||
/* send and receive audio */
|
||||
void call_up_audio(int callref, sample_t *samples, int count);
|
||||
void call_down_audio(int callref, sample_t *samples, int count);
|
||||
|
||||
/* clock to transmit to */
|
||||
void call_clock(void); /* from main loop */
|
||||
void call_down_clock(void); /* towards mobile implementation */
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
/* 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";
|
||||
case CAUSE_RESOURCE_UNAVAIL:
|
||||
return "resource-unavail";
|
||||
case CAUSE_INVALCALLREF:
|
||||
return "invalid-callref";
|
||||
default:
|
||||
sprintf(cause_str, "cause=%d", cause);
|
||||
return cause_str;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#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_RESOURCE_UNAVAIL 47
|
||||
#define CAUSE_INVALCALLREF 81
|
||||
|
||||
#define LOCATION_USER 0
|
||||
#define LOCATION_PRIVATE_LOCAL 1
|
||||
|
||||
const char *cause_name(int cause);
|
||||
|
||||
|
|
@ -1,636 +0,0 @@
|
|||
/* built-in console to talk to a phone
|
||||
*
|
||||
* (C) 2017 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
|
||||
G* 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 "../libsample/sample.h"
|
||||
#include "../libsamplerate/samplerate.h"
|
||||
#include "../libjitter/jitter.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libtimer/timer.h"
|
||||
#include "../libosmocc/endpoint.h"
|
||||
#include "../libosmocc/helper.h"
|
||||
#include "testton.h"
|
||||
#include "../libmobile/main_mobile.h"
|
||||
#include "console.h"
|
||||
#include "cause.h"
|
||||
#include "../libmobile/call.h"
|
||||
#ifdef HAVE_ALSA
|
||||
#include "../libsound/sound.h"
|
||||
#endif
|
||||
|
||||
enum console_state {
|
||||
CONSOLE_IDLE = 0, /* IDLE */
|
||||
CONSOLE_SETUP_RO, /* call from radio to console */
|
||||
CONSOLE_SETUP_RT, /* call from console to radio */
|
||||
CONSOLE_ALERTING_RO, /* call from radio to console */
|
||||
CONSOLE_ALERTING_RT, /* call from console to radio */
|
||||
CONSOLE_CONNECT,
|
||||
CONSOLE_DISCONNECT_RO,
|
||||
};
|
||||
|
||||
static const char *console_state_name[] = {
|
||||
"IDLE",
|
||||
"SETUP_RO",
|
||||
"SETUP_RT",
|
||||
"ALERTING_RO",
|
||||
"ALERTING_RT",
|
||||
"CONNECT",
|
||||
"DISCONNECT_RO",
|
||||
};
|
||||
|
||||
/* console call instance */
|
||||
typedef struct console {
|
||||
osmo_cc_session_t *session;
|
||||
osmo_cc_session_codec_t *codec;
|
||||
uint32_t callref;
|
||||
enum console_state state;
|
||||
int disc_cause; /* cause that has been sent by transceiver instance for release */
|
||||
char station_id[33];
|
||||
char dialing[33];
|
||||
char audiodev[64]; /* headphone interface, if used */
|
||||
int samplerate; /* sample rate of headphone interface */
|
||||
void *sound; /* headphone interface */
|
||||
int buffer_size; /* sample buffer size at headphone interface */
|
||||
samplerate_t srstate; /* patterns/announcement upsampling */
|
||||
jitter_t dejitter; /* headphone audio dejittering */
|
||||
int test_audio_pos; /* position for test tone toward mobile */
|
||||
sample_t tx_buffer[160];/* transmit audio buffer */
|
||||
int tx_buffer_pos; /* current position in transmit audio buffer */
|
||||
const struct number_lengths *number_lengths;/* number of digits to be dialed */
|
||||
int number_max_length; /* number of digits of the longest number to be dialed */
|
||||
int loopback; /* loopback test for echo */
|
||||
int echo_test; /* send echo back to mobile phone */
|
||||
const char *digits; /* list of dialable digits */
|
||||
} console_t;
|
||||
|
||||
static console_t console;
|
||||
|
||||
extern osmo_cc_endpoint_t *ep;
|
||||
|
||||
void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
|
||||
static struct osmo_cc_helper_audio_codecs codecs[] = {
|
||||
{ "L16", 8000, 1, encode_l16, decode_l16 },
|
||||
{ NULL, 0, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
/* stream test music */
|
||||
int16_t *test_spl = NULL;
|
||||
int test_size = 0;
|
||||
int test_max = 0;
|
||||
|
||||
static void get_test_patterns(int16_t *samples, int length)
|
||||
{
|
||||
const int16_t *spl;
|
||||
int size, max, pos;
|
||||
|
||||
spl = test_spl;
|
||||
size = test_size;
|
||||
max = test_max;
|
||||
|
||||
/* stream sample */
|
||||
pos = console.test_audio_pos;
|
||||
while(length--) {
|
||||
if (pos >= size)
|
||||
*samples++ = 0;
|
||||
else
|
||||
*samples++ = spl[pos] >> 2;
|
||||
if (++pos == max)
|
||||
pos = 0;
|
||||
}
|
||||
console.test_audio_pos = pos;
|
||||
}
|
||||
|
||||
static void console_new_state(enum console_state state)
|
||||
{
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Call state '%s' -> '%s'\n", console_state_name[console.state], console_state_name[state]);
|
||||
console.state = state;
|
||||
console.test_audio_pos = 0;
|
||||
}
|
||||
|
||||
static void free_console(void)
|
||||
{
|
||||
if (console.session) {
|
||||
osmo_cc_free_session(console.session);
|
||||
console.session = NULL;
|
||||
}
|
||||
console.codec = NULL;
|
||||
console.callref = 0;
|
||||
}
|
||||
|
||||
void up_audio(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len)
|
||||
{
|
||||
int count = len / 2;
|
||||
sample_t samples[count];
|
||||
|
||||
/* save audio from transceiver to jitter buffer */
|
||||
if (console.sound) {
|
||||
sample_t up[(int)((double)count * console.srstate.factor + 0.5) + 10];
|
||||
int16_to_samples(samples, (int16_t *)data, count);
|
||||
count = samplerate_upsample(&console.srstate, samples, count, up);
|
||||
jitter_save(&console.dejitter, up, count);
|
||||
return;
|
||||
}
|
||||
/* if echo test is used, send echo back to mobile */
|
||||
if (console.echo_test) {
|
||||
osmo_cc_rtp_send(codec, (uint8_t *)data, count * 2, 1, count);
|
||||
return;
|
||||
}
|
||||
/* if no sound is used, send test tone to mobile */
|
||||
if (console.state == CONSOLE_CONNECT) {
|
||||
get_test_patterns((int16_t *)data, count);
|
||||
osmo_cc_rtp_send(codec, (uint8_t *)data, count * 2, 1, count);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void request_setup(int callref, const char *dialing)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_REQ);
|
||||
/* called number */
|
||||
if (dialing)
|
||||
osmo_cc_add_ie_called(msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, dialing);
|
||||
/* bearer capability */
|
||||
osmo_cc_add_ie_bearer(msg, OSMO_CC_CODING_ITU_T, OSMO_CC_CAPABILITY_AUDIO, OSMO_CC_MODE_CIRCUIT);
|
||||
/* sdp offer */
|
||||
console.session = osmo_cc_helper_audio_offer(&ep->session_config, NULL, codecs, up_audio, msg, 1);
|
||||
|
||||
osmo_cc_ul_msg(ep, callref, msg);
|
||||
}
|
||||
|
||||
static void request_answer(int callref, const char *connectid, const char *sdp)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_RSP);
|
||||
/* calling number */
|
||||
if (connectid)
|
||||
osmo_cc_add_ie_calling(msg, OSMO_CC_TYPE_SUBSCRIBER, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_ALLOWED, OSMO_CC_SCREEN_NETWORK, connectid);
|
||||
/* SDP */
|
||||
if (sdp)
|
||||
osmo_cc_add_ie_sdp(msg, sdp);
|
||||
|
||||
osmo_cc_ul_msg(ep, callref, msg);
|
||||
}
|
||||
|
||||
static void request_answer_ack(int callref)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_COMP_REQ);
|
||||
|
||||
osmo_cc_ul_msg(ep, callref, msg);
|
||||
}
|
||||
|
||||
static void request_disconnect_release_reject(int callref, int cause, uint8_t msg_type)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
msg = osmo_cc_new_msg(msg_type);
|
||||
osmo_cc_add_ie_cause(msg, OSMO_CC_LOCATION_USER, cause, 0, 0);
|
||||
|
||||
osmo_cc_ul_msg(ep, callref, msg);
|
||||
}
|
||||
|
||||
void console_msg(osmo_cc_call_t *call, osmo_cc_msg_t *msg)
|
||||
{
|
||||
uint8_t location, isdn_cause, socket_cause;
|
||||
uint16_t sip_cause;
|
||||
uint8_t type, plan, present, screen;
|
||||
uint8_t progress, coding;
|
||||
char caller_id[33], number[33];
|
||||
const char *sdp;
|
||||
int rc;
|
||||
|
||||
if (msg->type != OSMO_CC_MSG_SETUP_IND && console.callref != call->callref) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "invalid call ref %u (msg=0x%02x).\n", call->callref, msg->type);
|
||||
request_disconnect_release_reject(call->callref, CAUSE_INVALCALLREF, OSMO_CC_MSG_REL_REQ);
|
||||
osmo_cc_free_msg(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(msg->type) {
|
||||
case OSMO_CC_MSG_SETUP_IND:
|
||||
{
|
||||
/* caller id */
|
||||
rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, caller_id, sizeof(caller_id));
|
||||
if (rc < 0)
|
||||
caller_id[0] = '\0';
|
||||
/* dialing */
|
||||
rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, number, sizeof(number));
|
||||
if (rc < 0)
|
||||
number[0] = '\0';
|
||||
PDEBUG(DCC, DEBUG_INFO, "Incoming call from '%s'\n", caller_id);
|
||||
/* setup is also allowed on disconnected call */
|
||||
if (console.state == CONSOLE_DISCONNECT_RO) {
|
||||
PDEBUG(DCC, DEBUG_INFO, "Releasing pending disconnected call\n");
|
||||
if (console.callref) {
|
||||
request_disconnect_release_reject(console.callref, CAUSE_NORMAL, OSMO_CC_MSG_REL_REQ);
|
||||
free_console();
|
||||
}
|
||||
console_new_state(CONSOLE_IDLE);
|
||||
}
|
||||
if (console.state != CONSOLE_IDLE) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "We are busy, rejecting.\n");
|
||||
request_disconnect_release_reject(console.callref, CAUSE_NORMAL, OSMO_CC_MSG_REJ_REQ);
|
||||
osmo_cc_free_msg(msg);
|
||||
return;
|
||||
}
|
||||
console.callref = call->callref;
|
||||
/* sdp accept */
|
||||
sdp = osmo_cc_helper_audio_accept(&ep->session_config, NULL, codecs, up_audio, msg, &console.session, &console.codec, 0);
|
||||
if (!sdp) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Cannot accept codec, rejecting.\n");
|
||||
request_disconnect_release_reject(console.callref, CAUSE_RESOURCE_UNAVAIL, OSMO_CC_MSG_REJ_REQ);
|
||||
osmo_cc_free_msg(msg);
|
||||
return;
|
||||
}
|
||||
if (caller_id[0]) {
|
||||
strncpy(console.station_id, caller_id, sizeof(console.station_id));
|
||||
console.station_id[sizeof(console.station_id) - 1] = '\0';
|
||||
}
|
||||
strncpy(console.dialing, number, sizeof(console.dialing) - 1);
|
||||
console.dialing[sizeof(console.dialing) - 1] = '\0';
|
||||
console_new_state(CONSOLE_CONNECT);
|
||||
PDEBUG(DCC, DEBUG_INFO, "Call automatically answered\n");
|
||||
request_answer(console.callref, number, sdp);
|
||||
break;
|
||||
}
|
||||
case OSMO_CC_MSG_SETUP_ACK_IND:
|
||||
case OSMO_CC_MSG_PROC_IND:
|
||||
osmo_cc_helper_audio_negotiate(msg, &console.session, &console.codec);
|
||||
break;
|
||||
case OSMO_CC_MSG_ALERT_IND:
|
||||
PDEBUG(DCC, DEBUG_INFO, "Call alerting\n");
|
||||
osmo_cc_helper_audio_negotiate(msg, &console.session, &console.codec);
|
||||
console_new_state(CONSOLE_ALERTING_RT);
|
||||
break;
|
||||
case OSMO_CC_MSG_SETUP_CNF:
|
||||
{
|
||||
/* connected id */
|
||||
rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, caller_id, sizeof(caller_id));
|
||||
if (rc < 0)
|
||||
caller_id[0] = '\0';
|
||||
PDEBUG(DCC, DEBUG_INFO, "Call connected to '%s'\n", caller_id);
|
||||
osmo_cc_helper_audio_negotiate(msg, &console.session, &console.codec);
|
||||
console_new_state(CONSOLE_CONNECT);
|
||||
if (caller_id[0]) {
|
||||
strncpy(console.station_id, caller_id, sizeof(console.station_id));
|
||||
console.station_id[sizeof(console.station_id) - 1] = '\0';
|
||||
}
|
||||
request_answer_ack(console.callref);
|
||||
break;
|
||||
}
|
||||
case OSMO_CC_MSG_SETUP_COMP_IND:
|
||||
break;
|
||||
case OSMO_CC_MSG_DISC_IND:
|
||||
rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
||||
if (rc < 0)
|
||||
isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR;
|
||||
rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress);
|
||||
osmo_cc_helper_audio_negotiate(msg, &console.session, &console.codec);
|
||||
if (rc >= 0 && (progress == 1 || progress == 8)) {
|
||||
PDEBUG(DCC, DEBUG_INFO, "Call disconnected with audio (%s)\n", cause_name(isdn_cause));
|
||||
console_new_state(CONSOLE_DISCONNECT_RO);
|
||||
console.disc_cause = isdn_cause;
|
||||
} else {
|
||||
PDEBUG(DCC, DEBUG_INFO, "Call disconnected without audio (%s)\n", cause_name(isdn_cause));
|
||||
request_disconnect_release_reject(console.callref, isdn_cause, OSMO_CC_MSG_REL_REQ);
|
||||
console_new_state(CONSOLE_IDLE);
|
||||
free_console();
|
||||
}
|
||||
break;
|
||||
case OSMO_CC_MSG_REL_IND:
|
||||
case OSMO_CC_MSG_REJ_IND:
|
||||
rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
||||
if (rc < 0)
|
||||
isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR;
|
||||
PDEBUG(DCC, DEBUG_INFO, "Call released (%s)\n", cause_name(isdn_cause));
|
||||
console_new_state(CONSOLE_IDLE);
|
||||
free_console();
|
||||
break;
|
||||
}
|
||||
osmo_cc_free_msg(msg);
|
||||
}
|
||||
|
||||
static char console_text[256];
|
||||
static char console_clear[256];
|
||||
static int console_len = 0;
|
||||
|
||||
static void _clear_console_text(void)
|
||||
{
|
||||
if (!console_len)
|
||||
return;
|
||||
|
||||
fwrite(console_clear, console_len, 1, stdout);
|
||||
// note: fflused by user of this function
|
||||
console_len = 0;
|
||||
}
|
||||
|
||||
static void _print_console_text(void)
|
||||
{
|
||||
if (!console_len)
|
||||
return;
|
||||
|
||||
printf("\033[1;37m");
|
||||
fwrite(console_text, console_len, 1, stdout);
|
||||
printf("\033[0;39m");
|
||||
}
|
||||
|
||||
int console_init(const char *audiodev, int samplerate, int buffer, int loopback, int echo_test, const char *digits, const struct number_lengths *lengths, const char *station_id)
|
||||
{
|
||||
int rc = 0;
|
||||
int i;
|
||||
|
||||
init_testton();
|
||||
|
||||
clear_console_text = _clear_console_text;
|
||||
print_console_text = _print_console_text;
|
||||
|
||||
memset(&console, 0, sizeof(console));
|
||||
strncpy(console.audiodev, audiodev, sizeof(console.audiodev) - 1);
|
||||
console.samplerate = samplerate;
|
||||
console.buffer_size = buffer * samplerate / 1000;
|
||||
console.loopback = loopback;
|
||||
console.echo_test = echo_test;
|
||||
console.digits = digits;
|
||||
console.number_lengths = lengths;
|
||||
if (lengths) {
|
||||
for (i = 0; lengths[i].usage; i++) {
|
||||
if (lengths[i].digits > console.number_max_length)
|
||||
console.number_max_length = lengths[i].digits;
|
||||
}
|
||||
}
|
||||
if (station_id)
|
||||
strncpy(console.station_id, station_id, sizeof(console.station_id) - 1);
|
||||
|
||||
if (!audiodev[0])
|
||||
return 0;
|
||||
|
||||
rc = init_samplerate(&console.srstate, 8000.0, (double)samplerate, 3300.0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to init sample rate conversion!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = jitter_create(&console.dejitter, samplerate / 5);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init dejitter buffer!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
console_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int console_open_audio(int __attribute__((unused)) buffer_size, double __attribute__((unused)) interval)
|
||||
{
|
||||
if (!console.audiodev[0])
|
||||
return 0;
|
||||
|
||||
#ifdef HAVE_ALSA
|
||||
/* open sound device for call control */
|
||||
/* use factor 1.4 of speech level for complete range of sound card */
|
||||
console.sound = sound_open(console.audiodev, NULL, NULL, NULL, 1, 0.0, console.samplerate, buffer_size, interval, 1.4, 4000.0, 2.0);
|
||||
if (!console.sound) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "No sound device!\n");
|
||||
return -EIO;
|
||||
}
|
||||
#else
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "No sound card support compiled in!\n");
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int console_start_audio(void)
|
||||
{
|
||||
if (!console.audiodev[0])
|
||||
return 0;
|
||||
|
||||
#ifdef HAVE_ALSA
|
||||
return sound_start(console.sound);
|
||||
#else
|
||||
return -EINVAL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void console_cleanup(void)
|
||||
{
|
||||
#ifdef HAVE_ALSA
|
||||
/* close sound devoice */
|
||||
if (console.sound) {
|
||||
sound_close(console.sound);
|
||||
console.sound = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
jitter_destroy(&console.dejitter);
|
||||
|
||||
if (console.session) {
|
||||
osmo_cc_free_session(console.session);
|
||||
console.session = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* process input from console
|
||||
* it is not called at loopback mode
|
||||
* calling this implies that the console.number_lengths is set
|
||||
*/
|
||||
static void process_ui(int c)
|
||||
{
|
||||
char text[256] = "";
|
||||
int len;
|
||||
int i;
|
||||
|
||||
switch (console.state) {
|
||||
case CONSOLE_IDLE:
|
||||
if (c > 0) {
|
||||
if ((int)strlen(console.station_id) < console.number_max_length) {
|
||||
for (i = 0; i < (int)strlen(console.digits); i++) {
|
||||
if (c == console.digits[i]) {
|
||||
console.station_id[strlen(console.station_id) + 1] = '\0';
|
||||
console.station_id[strlen(console.station_id)] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((c == 8 || c == 127) && strlen(console.station_id))
|
||||
console.station_id[strlen(console.station_id) - 1] = '\0';
|
||||
dial_after_hangup:
|
||||
len = strlen(console.station_id);
|
||||
for (i = 0; console.number_lengths[i].usage; i++) {
|
||||
if (len == console.number_lengths[i].digits)
|
||||
break;
|
||||
}
|
||||
if (c == 'd' && console.number_lengths[i].usage) {
|
||||
PDEBUG(DCC, DEBUG_INFO, "Outgoing call to '%s'\n", console.station_id);
|
||||
console.dialing[0] = '\0';
|
||||
console_new_state(CONSOLE_SETUP_RT);
|
||||
console.callref = osmo_cc_new_callref();
|
||||
request_setup(console.callref, console.station_id);
|
||||
}
|
||||
}
|
||||
sprintf(text, "on-hook: %s%s ", console.station_id, "................................" + 32 - console.number_max_length + strlen(console.station_id));
|
||||
len = strlen(console.station_id);
|
||||
for (i = 0; console.number_lengths[i].usage; i++) {
|
||||
if (len == console.number_lengths[i].digits)
|
||||
break;
|
||||
}
|
||||
if (console.number_lengths[i].usage) {
|
||||
if (console.number_lengths[i + 1].usage)
|
||||
sprintf(strchr(text, '\0'), "(enter digits %s or press d=dial)\r", console.digits);
|
||||
else
|
||||
sprintf(strchr(text, '\0'), "(press d=dial)\r");
|
||||
} else
|
||||
sprintf(strchr(text, '\0'), "(enter digits %s)\r", console.digits);
|
||||
break;
|
||||
case CONSOLE_SETUP_RO:
|
||||
case CONSOLE_SETUP_RT:
|
||||
case CONSOLE_ALERTING_RO:
|
||||
case CONSOLE_ALERTING_RT:
|
||||
case CONSOLE_CONNECT:
|
||||
case CONSOLE_DISCONNECT_RO:
|
||||
if (c > 0) {
|
||||
if (c == 'h' || (c == 'd' && console.state == CONSOLE_DISCONNECT_RO)) {
|
||||
PDEBUG(DCC, DEBUG_INFO, "Call hangup\n");
|
||||
if (console.callref) {
|
||||
if (console.state == CONSOLE_SETUP_RO)
|
||||
request_disconnect_release_reject(console.callref, CAUSE_NORMAL, OSMO_CC_MSG_REJ_REQ);
|
||||
else
|
||||
request_disconnect_release_reject(console.callref, CAUSE_NORMAL, OSMO_CC_MSG_REL_REQ);
|
||||
free_console();
|
||||
}
|
||||
console_new_state(CONSOLE_IDLE);
|
||||
if (c == 'd')
|
||||
goto dial_after_hangup;
|
||||
}
|
||||
}
|
||||
if (console.state == CONSOLE_SETUP_RT)
|
||||
sprintf(text, "call setup: %s (press h=hangup)\r", console.station_id);
|
||||
if (console.state == CONSOLE_ALERTING_RT)
|
||||
sprintf(text, "call ringing: %s (press h=hangup)\r", console.station_id);
|
||||
if (console.state == CONSOLE_CONNECT) {
|
||||
if (console.dialing[0])
|
||||
sprintf(text, "call active: %s->%s (press h=hangup)\r", console.station_id, console.dialing);
|
||||
else
|
||||
sprintf(text, "call active: %s (press h=hangup)\r", console.station_id);
|
||||
}
|
||||
if (console.state == CONSOLE_DISCONNECT_RO)
|
||||
sprintf(text, "call disconnected: %s (press h=hangup d=redial)\r", cause_name(console.disc_cause));
|
||||
break;
|
||||
}
|
||||
/* skip if nothing has changed */
|
||||
len = strlen(text);
|
||||
if (console_len == len && !memcmp(console_text, text, len))
|
||||
return;
|
||||
clear_console_text();
|
||||
console_len = len;
|
||||
memcpy(console_text, text, len);
|
||||
if (len) {
|
||||
memset(console_clear, ' ', len - 1);
|
||||
console_clear[len - 1] = '\r';
|
||||
}
|
||||
print_console_text();
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* get keys from keyboard to control call via console
|
||||
* returns 1 on exit (ctrl+c) */
|
||||
void process_console(int c)
|
||||
{
|
||||
if (!console.loopback && console.number_max_length)
|
||||
process_ui(c);
|
||||
|
||||
if (console.session)
|
||||
osmo_cc_session_handle(console.session);
|
||||
|
||||
if (!console.sound)
|
||||
return;
|
||||
|
||||
#ifdef HAVE_ALSA
|
||||
/* handle audio, if sound device is used */
|
||||
sample_t samples[console.buffer_size + 10], *samples_list[1];
|
||||
uint8_t *power_list[1];
|
||||
int count;
|
||||
int rc;
|
||||
|
||||
count = sound_get_tosend(console.sound, console.buffer_size);
|
||||
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 > 0) {
|
||||
jitter_load(&console.dejitter, samples, count);
|
||||
samples_list[0] = samples;
|
||||
power_list[0] = NULL;
|
||||
rc = sound_write(console.sound, samples_list, power_list, count, NULL, NULL, 1);
|
||||
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;
|
||||
}
|
||||
}
|
||||
samples_list[0] = samples;
|
||||
count = sound_read(console.sound, samples_list, console.buffer_size, 1, NULL);
|
||||
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) {
|
||||
int i;
|
||||
|
||||
if (console.loopback == 3)
|
||||
jitter_save(&console.dejitter, samples, count);
|
||||
count = samplerate_downsample(&console.srstate, samples, count);
|
||||
/* put samples into ring buffer */
|
||||
for (i = 0; i < count; i++) {
|
||||
console.tx_buffer[console.tx_buffer_pos] = samples[i];
|
||||
/* if ring buffer wraps, deliver data down to call process */
|
||||
if (++console.tx_buffer_pos == 160) {
|
||||
console.tx_buffer_pos = 0;
|
||||
/* only if we have a call */
|
||||
if (console.callref && console.codec) {
|
||||
int16_t data[160];
|
||||
samples_to_int16(data, console.tx_buffer, 160);
|
||||
osmo_cc_rtp_send(console.codec, (uint8_t *)data, 160 * 2, 1, 160);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
void console_msg(osmo_cc_call_t *call, osmo_cc_msg_t *msg);
|
||||
int console_init(const char *audiodev, int samplerate, int buffer, int loopback, int echo_test, const char *digits, const struct number_lengths *lengths, const char *station_id);
|
||||
void console_cleanup(void);
|
||||
int console_open_audio(int buffer_size, double interval);
|
||||
int console_start_audio(void);
|
||||
void console_process(int c);
|
||||
void process_console(int c);
|
||||
|
|
@ -1,857 +0,0 @@
|
|||
/* 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
#include <termios.h>
|
||||
#include <errno.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "main_mobile.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "sender.h"
|
||||
#include "../libtimer/timer.h"
|
||||
#include "call.h"
|
||||
#include "../libosmocc/endpoint.h"
|
||||
#include "console.h"
|
||||
#ifdef HAVE_SDR
|
||||
#include "../libsdr/sdr.h"
|
||||
#include "../libsdr/sdr_config.h"
|
||||
#endif
|
||||
#include "../liboptions/options.h"
|
||||
#include "../libfm/fm.h"
|
||||
#include "../libaaimage/aaimage.h"
|
||||
|
||||
#define DEFAULT_LO_OFFSET -1000000.0
|
||||
|
||||
static int got_init = 0;
|
||||
|
||||
/* common mobile settings */
|
||||
int num_kanal = 0;
|
||||
const char *kanal[MAX_SENDER];
|
||||
int num_device = 0;
|
||||
const char *dsp_device[MAX_SENDER] = { "hw:0,0" };
|
||||
int allow_sdr = 1;
|
||||
int use_sdr = 0;
|
||||
int dsp_samplerate = 48000;
|
||||
double dsp_interval = 1.0;
|
||||
int dsp_buffer = 50;
|
||||
static const char *call_device = "";
|
||||
static int call_samplerate = 48000;
|
||||
static int call_buffer = 50;
|
||||
int uses_emphasis = 1;
|
||||
int do_pre_emphasis = 0;
|
||||
int do_de_emphasis = 0;
|
||||
double rx_gain = 1.0;
|
||||
double tx_gain = 1.0;
|
||||
static int echo_test = 0;
|
||||
static int use_osmocc_cross = 0;
|
||||
static int use_osmocc_sock = 0;
|
||||
#define MAX_CC_ARGS 1024
|
||||
static int cc_argc = 0;
|
||||
static const char *cc_argv[MAX_CC_ARGS];
|
||||
int send_patterns = 1;
|
||||
static int release_on_disconnect = 1;
|
||||
int loopback = 0;
|
||||
int rt_prio = 1;
|
||||
int fast_math = 0;
|
||||
const char *write_tx_wave = NULL;
|
||||
const char *write_rx_wave = NULL;
|
||||
const char *read_tx_wave = NULL;
|
||||
const char *read_rx_wave = NULL;
|
||||
|
||||
static const char *number_digits;
|
||||
static const struct number_lengths *number_lengths;
|
||||
static const char **number_prefixes;
|
||||
|
||||
const char *mobile_number_remove_prefix(const char *number)
|
||||
{
|
||||
size_t len;
|
||||
int i, j;
|
||||
|
||||
if (!number_prefixes)
|
||||
return number;
|
||||
|
||||
len = strlen(number);
|
||||
for (i = 0; number_prefixes[i]; i++) {
|
||||
/* skip different lengths */
|
||||
if (len != strlen(number_prefixes[i]))
|
||||
continue;
|
||||
/* match prefix, stop at 'x' */
|
||||
for (j = 0; number_prefixes[i][j]; j++) {
|
||||
if (number_prefixes[i][j] == 'x')
|
||||
break;
|
||||
if (number_prefixes[i][j] != number[j])
|
||||
break;
|
||||
}
|
||||
/* if prefix matches, return suffix */
|
||||
if (number_prefixes[i][j] == 'x')
|
||||
return number + j;
|
||||
}
|
||||
|
||||
/* return number, if there is no prefix matching */
|
||||
return number;
|
||||
}
|
||||
|
||||
const char *mobile_number_check_length(const char *number)
|
||||
{
|
||||
size_t len;
|
||||
int i;
|
||||
static char invalid[256];
|
||||
|
||||
if (!number_lengths)
|
||||
return NULL;
|
||||
|
||||
len = strlen(number);
|
||||
for (i = 0; number_lengths[i].usage; i++) {
|
||||
if ((int)len == number_lengths[i].digits)
|
||||
break;
|
||||
}
|
||||
if (!number_lengths[i].usage) {
|
||||
sprintf(invalid, "Number does not have");
|
||||
for (i = 0; number_lengths[i].usage; i++) {
|
||||
sprintf(strchr(invalid, '\0'), " %d", number_lengths[i].digits);
|
||||
if (number_lengths[i + 1].usage) {
|
||||
if (number_lengths[i + 2].usage)
|
||||
strcat(invalid, ",");
|
||||
else
|
||||
strcat(invalid, " or");
|
||||
}
|
||||
}
|
||||
sprintf(strchr(invalid, '\0'), " digits.");
|
||||
return invalid;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *mobile_number_check_digits(const char *number)
|
||||
{
|
||||
int i;
|
||||
static char invalid[256];
|
||||
|
||||
for (i = 0; number[i]; i++) {
|
||||
if (!strchr(number_digits, number[i])) {
|
||||
sprintf(invalid, "Digit #%d of number has digit '%c' which is not in the set of allowed digits. ('%s')\n", i + 1, number[i], number_digits);
|
||||
return invalid;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *(*mobile_number_check_valid)(const char *);
|
||||
|
||||
void main_mobile_init(const char *digits, const struct number_lengths lengths[], const char *prefixes[], const char *(*check_valid)(const char *))
|
||||
{
|
||||
cc_argv[cc_argc++] = options_strdup("remote auto");
|
||||
|
||||
number_digits = digits;
|
||||
number_lengths = lengths;
|
||||
number_prefixes = prefixes;
|
||||
mobile_number_check_valid = check_valid;
|
||||
|
||||
got_init = 1;
|
||||
#ifdef HAVE_SDR
|
||||
sdr_config_init(DEFAULT_LO_OFFSET);
|
||||
#endif
|
||||
}
|
||||
|
||||
void main_mobile_set_number_check_valid(const char *(*check_valid)(const char *))
|
||||
{
|
||||
mobile_number_check_valid = check_valid;
|
||||
}
|
||||
|
||||
/* ask if number is connect */
|
||||
int main_mobile_number_ask(const char *number, const char *what)
|
||||
{
|
||||
const char *invalid;
|
||||
|
||||
if (!got_init) {
|
||||
fprintf(stderr, "main_mobile_init was not called, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
number = mobile_number_remove_prefix(number);
|
||||
|
||||
invalid = mobile_number_check_length(number);
|
||||
if (invalid) {
|
||||
printf("Given %s '%s' has invalid length: %s\n", what, number, invalid);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
invalid = mobile_number_check_digits(number);
|
||||
if (invalid) {
|
||||
printf("Given %s '%s' has invalid digit: %s\n", what, number, invalid);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (mobile_number_check_valid) {
|
||||
invalid = mobile_number_check_valid(number);
|
||||
if (invalid) {
|
||||
printf("Given %s '%s' is invalid for this network: %s\n", what, number, invalid);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void main_mobile_print_help(const char *arg0, const char *ext_usage)
|
||||
{
|
||||
printf("Usage: %s -k <kanal/channel> %s[options] [station_id]\n", arg0, ext_usage);
|
||||
printf("\nGlobal options:\n");
|
||||
/* - - */
|
||||
printf(" -h --help\n");
|
||||
printf(" This help\n");
|
||||
printf(" --config [~/]<path to config file>\n");
|
||||
printf(" Give a config file to use. If it starts with '~/', path is at home dir.\n");
|
||||
printf(" Each line in config file is one option, '-' or '--' must not be given!\n");
|
||||
debug_print_help();
|
||||
printf(" -k --kanal <channel>\n");
|
||||
printf(" -k --channel <channel>\n");
|
||||
printf(" Channel (German = Kanal) number of \"Sender\" (German = Transceiver)\n");
|
||||
printf(" -a --audio-device hw:<card>,<device>\n");
|
||||
printf(" Sound card and device number (default = '%s')\n", dsp_device[0]);
|
||||
printf(" Don't set it for SDR!\n");
|
||||
printf(" -s --samplerate <rate>\n");
|
||||
printf(" Sample rate of sound device (default = '%d')\n", dsp_samplerate);
|
||||
printf(" -i --interval 0.1..25\n");
|
||||
printf(" Interval of processing loop in ms (default = '%.1f' ms)\n", dsp_interval);
|
||||
printf(" Use 10 to drastically reduce CPU usage. In case of buffer underrun,\n");
|
||||
printf(" increase buffer accordingly.\n");
|
||||
printf(" -b --buffer <ms>\n");
|
||||
printf(" How many milliseconds are processed in advance (default = '%d')\n", dsp_buffer);
|
||||
printf(" A buffer below 10 ms requires low interval like 0.1 ms.\n");
|
||||
if (uses_emphasis) {
|
||||
printf(" -p --pre-emphasis\n");
|
||||
printf(" Enable pre-emphasis, if you directly connect to the oscillator of the\n");
|
||||
printf(" transmitter. (No pre-emphasis done by the transmitter.)\n");
|
||||
printf(" -d --de-emphasis\n");
|
||||
printf(" Enable de-emphasis, if you directly connect to the discriminator of\n");
|
||||
printf(" the receiver. (No de-emphasis done by the receiver.)\n");
|
||||
}
|
||||
printf(" --rx-gain <dB>\n");
|
||||
printf(" Raise/lower receiver's RX level by given gain in dB.\n");
|
||||
printf(" (Works with sound card only.)\n");
|
||||
printf(" --tx-gain <dB>\n");
|
||||
printf(" Raise/lower transmitters's RX level by given gain in dB.\n");
|
||||
printf(" (Works with sound card only.)\n");
|
||||
printf(" -e --echo-test\n");
|
||||
printf(" Use echo test, to send back audio from mobile phone's microphone to\n");
|
||||
printf(" the speaker. (German: 'Blasprobe').\n");
|
||||
printf(" -c --call-device hw:<card>,<device>\n");
|
||||
printf(" Sound card and device number for headset (default = '%s')\n", call_device);
|
||||
printf(" --call-samplerate <rate>\n");
|
||||
printf(" Sample rate of sound device for headset (default = '%d')\n", call_samplerate);
|
||||
printf(" --call-buffer <ms>\n");
|
||||
printf(" How many milliseconds are processed in advance (default = '%d')\n", call_buffer);
|
||||
printf(" -x --osmocc-cross\n");
|
||||
printf(" Enable built-in call forwarding between mobiles. Be sure to have\n");
|
||||
printf(" at least one control channel and two voice channels. Alternatively\n");
|
||||
printf(" use one combined control+voice channel and one voice channels.\n");
|
||||
printf(" -o --osmocc-sock\n");
|
||||
printf(" Disable built-in call control and offer socket\n");
|
||||
printf(" --cc \"<osmo-cc arg>\" [--cc ...]\n");
|
||||
printf(" Pass arguments to Osmo-CC endpoint. Use '-cc help' for description.\n");
|
||||
printf(" -t --tones 0 | 1\n");
|
||||
printf(" Connect call on setup/release to provide classic tones towards fixed\n");
|
||||
printf(" network (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 disable, 99 for maximum (default = %d)\n", rt_prio);
|
||||
printf(" --fast-math\n");
|
||||
printf(" Use fast math approximation for slow CPU / ARM based systems.\n");
|
||||
printf(" --write-rx-wave <file>\n");
|
||||
printf(" Write received audio to given wave file.\n");
|
||||
printf(" --write-tx-wave <file>\n");
|
||||
printf(" Write transmitted audio to given wave file.\n");
|
||||
printf(" --read-rx-wave <file>\n");
|
||||
printf(" Replace received audio by given wave file.\n");
|
||||
printf(" --read-tx-wave <file>\n");
|
||||
printf(" Replace transmitted audio by given wave file.\n");
|
||||
#ifdef HAVE_SDR
|
||||
if (allow_sdr) {
|
||||
printf(" --limesdr\n");
|
||||
printf(" Auto-select several required options for LimeSDR\n");
|
||||
printf(" --limesdr-mini\n");
|
||||
printf(" Auto-select several required options for LimeSDR Mini\n");
|
||||
sdr_config_print_help();
|
||||
}
|
||||
#endif
|
||||
printf("\nNetwork specific options:\n");
|
||||
}
|
||||
|
||||
void main_mobile_print_station_id(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!number_lengths)
|
||||
return;
|
||||
|
||||
printf("\nstation_id: Give");
|
||||
for (i = 0; number_lengths[i].usage; i++) {
|
||||
printf(" %d", number_lengths[i].digits);
|
||||
if (number_lengths[i + 1].usage) {
|
||||
if (number_lengths[i + 2].usage)
|
||||
printf(",");
|
||||
else
|
||||
printf(" or");
|
||||
}
|
||||
}
|
||||
printf(" digits of station ID,\n");
|
||||
printf(" so you don't need to enter it for every start of this application.\n");
|
||||
for (i = 0; number_lengths[i].usage; i++)
|
||||
printf(" Give %d digits for %s.\n", number_lengths[i].digits, number_lengths[i].usage);
|
||||
if (number_prefixes) {
|
||||
for (i = 0; number_prefixes[i]; i++)
|
||||
printf(" You may use '%s' as prefix.\n", number_prefixes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void main_mobile_print_hotkeys(void)
|
||||
{
|
||||
printf("\n");
|
||||
printf("Press digits '0'..'9' and then 'd' key to dial towards mobile station.\n");
|
||||
printf("Press 'h' key to hangup.\n");
|
||||
printf("Press 'w' key to toggle display of RX wave form.\n");
|
||||
printf("Press 'c' key to toggle display of channel status.\n");
|
||||
printf("Press 'm' key to toggle display of measurement value.\n");
|
||||
#ifdef HAVE_SDR
|
||||
if (allow_sdr) {
|
||||
sdr_config_print_hotkeys();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#define OPT_CHANNEL 1000
|
||||
#define OPT_RX_GAIN 1001
|
||||
#define OPT_TX_GAIN 1002
|
||||
#define OPT_OSMO_CC 1003
|
||||
#define OPT_WRITE_RX_WAVE 1004
|
||||
#define OPT_WRITE_TX_WAVE 1005
|
||||
#define OPT_READ_RX_WAVE 1006
|
||||
#define OPT_READ_TX_WAVE 1007
|
||||
#define OPT_CALL_SAMPLERATE 1008
|
||||
#define OPT_CALL_BUFFER 1009
|
||||
#define OPT_FAST_MATH 1010
|
||||
#define OPT_LIMESDR 1100
|
||||
#define OPT_LIMESDR_MINI 1101
|
||||
|
||||
void main_mobile_add_options(void)
|
||||
{
|
||||
option_add('h', "help", 0);
|
||||
option_add('v', "verbose", 1);
|
||||
option_add('k', "kanal", 1);
|
||||
option_add(OPT_CHANNEL, "channel", 1);
|
||||
option_add('a', "audio-device", 1);
|
||||
option_add('s', "samplerate", 1);
|
||||
option_add('i', "interval", 1);
|
||||
option_add('b', "buffer", 1);
|
||||
option_add('p', "pre-emphasis", 0);
|
||||
option_add('d', "de-emphasis", 0);
|
||||
option_add(OPT_RX_GAIN, "rx-gain", 1);
|
||||
option_add(OPT_TX_GAIN, "tx-gain", 1);
|
||||
option_add('e', "echo-test", 0);
|
||||
option_add('x', "osmocc-cross", 0);
|
||||
option_add('o', "osmocc-sock", 0);
|
||||
option_add(OPT_OSMO_CC, "cc", 1);
|
||||
option_add('c', "call-device", 1);
|
||||
option_add(OPT_CALL_SAMPLERATE, "call-samplerate", 1);
|
||||
option_add(OPT_CALL_BUFFER, "call-buffer", 1);
|
||||
option_add('t', "tones", 1);
|
||||
option_add('l', "loopback", 1);
|
||||
option_add('r', "realtime", 1);
|
||||
option_add(OPT_FAST_MATH, "fast-math", 0);
|
||||
option_add(OPT_WRITE_RX_WAVE, "write-rx-wave", 1);
|
||||
option_add(OPT_WRITE_TX_WAVE, "write-tx-wave", 1);
|
||||
option_add(OPT_READ_RX_WAVE, "read-rx-wave", 1);
|
||||
option_add(OPT_READ_TX_WAVE, "read-tx-wave", 1);
|
||||
#ifdef HAVE_SDR
|
||||
option_add(OPT_LIMESDR, "limesdr", 0);
|
||||
option_add(OPT_LIMESDR_MINI, "limesdr-mini", 0);
|
||||
sdr_config_add_options();
|
||||
#endif
|
||||
};
|
||||
|
||||
void print_help(const char *arg0);
|
||||
|
||||
int main_mobile_handle_options(int short_option, int argi, char **argv)
|
||||
{
|
||||
double gain_db;
|
||||
int rc;
|
||||
|
||||
switch (short_option) {
|
||||
case 'h':
|
||||
print_help(argv[0]);
|
||||
return 0;
|
||||
case 'v':
|
||||
if (!strcasecmp(argv[argi], "list")) {
|
||||
debug_list_cat();
|
||||
return 0;
|
||||
}
|
||||
rc = parse_debug_opt(argv[argi]);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to parse debug option, please use -h for help.\n");
|
||||
return rc;
|
||||
}
|
||||
break;
|
||||
case 'k':
|
||||
case OPT_CHANNEL:
|
||||
OPT_ARRAY(num_kanal, kanal, argv[argi])
|
||||
break;
|
||||
case 'a':
|
||||
OPT_ARRAY(num_device, dsp_device, options_strdup(argv[argi]))
|
||||
break;
|
||||
case 's':
|
||||
dsp_samplerate = atoi(argv[argi]);
|
||||
break;
|
||||
case 'i':
|
||||
dsp_interval = atof(argv[argi]);
|
||||
if (dsp_interval < 0.1)
|
||||
dsp_interval = 0.1;
|
||||
if (dsp_interval > 10)
|
||||
dsp_interval = 10;
|
||||
break;
|
||||
case 'b':
|
||||
dsp_buffer = atoi(argv[argi]);
|
||||
break;
|
||||
case 'p':
|
||||
if (!uses_emphasis) {
|
||||
no_emph:
|
||||
fprintf(stderr, "This network does not use emphasis, please do not enable pre- or de-emphasis! Disable emphasis on transceiver, if possible.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
do_pre_emphasis = 1;
|
||||
break;
|
||||
case 'd':
|
||||
if (!uses_emphasis)
|
||||
goto no_emph;
|
||||
do_de_emphasis = 1;
|
||||
break;
|
||||
case OPT_RX_GAIN:
|
||||
gain_db = atof(argv[argi]);
|
||||
rx_gain = pow(10, gain_db / 20.0);
|
||||
break;
|
||||
case OPT_TX_GAIN:
|
||||
gain_db = atof(argv[argi]);
|
||||
tx_gain = pow(10, gain_db / 20.0);
|
||||
break;
|
||||
case 'e':
|
||||
echo_test = 1;
|
||||
break;
|
||||
case 'x':
|
||||
use_osmocc_cross = 1;
|
||||
break;
|
||||
case 'o':
|
||||
use_osmocc_sock = 1;
|
||||
break;
|
||||
case OPT_OSMO_CC:
|
||||
if (!strcasecmp(argv[argi], "help")) {
|
||||
osmo_cc_help();
|
||||
return 0;
|
||||
}
|
||||
if (cc_argc == MAX_CC_ARGS) {
|
||||
fprintf(stderr, "Too many osmo-cc args!\n");
|
||||
break;
|
||||
}
|
||||
cc_argv[cc_argc++] = options_strdup(argv[argi]);
|
||||
break;
|
||||
case 'c':
|
||||
call_device = options_strdup(argv[argi]);
|
||||
break;
|
||||
case OPT_CALL_SAMPLERATE:
|
||||
call_samplerate = atoi(argv[argi]);
|
||||
break;
|
||||
case OPT_CALL_BUFFER:
|
||||
call_buffer = atoi(argv[argi]);
|
||||
break;
|
||||
case 't':
|
||||
send_patterns = atoi(argv[argi]);
|
||||
break;
|
||||
case 'l':
|
||||
loopback = atoi(argv[argi]);
|
||||
break;
|
||||
case 'r':
|
||||
rt_prio = atoi(argv[argi]);
|
||||
break;
|
||||
case OPT_FAST_MATH:
|
||||
fast_math = 1;
|
||||
break;
|
||||
case OPT_WRITE_RX_WAVE:
|
||||
write_rx_wave = options_strdup(argv[argi]);
|
||||
break;
|
||||
case OPT_WRITE_TX_WAVE:
|
||||
write_tx_wave = options_strdup(argv[argi]);
|
||||
break;
|
||||
case OPT_READ_RX_WAVE:
|
||||
read_rx_wave = options_strdup(argv[argi]);
|
||||
break;
|
||||
case OPT_READ_TX_WAVE:
|
||||
read_tx_wave = options_strdup(argv[argi]);
|
||||
break;
|
||||
#ifdef HAVE_SDR
|
||||
case OPT_LIMESDR:
|
||||
if (allow_sdr) {
|
||||
char *argv_lime[] = { argv[0],
|
||||
"--sdr-soapy",
|
||||
"--sdr-device-args", "driver=lime",
|
||||
"--sdr-rx-antenna", "LNAL",
|
||||
"--sdr-rx-gain", "30",
|
||||
"--sdr-tx-gain", "30",
|
||||
"--sdr-samplerate", "5000000",
|
||||
"--sdr-bandwidth", "15000000",
|
||||
"-s", "200000",
|
||||
};
|
||||
int argc_lime = sizeof(argv_lime) / sizeof (*argv_lime);
|
||||
return options_command_line(argc_lime, argv_lime, main_mobile_handle_options);
|
||||
}
|
||||
break;
|
||||
case OPT_LIMESDR_MINI:
|
||||
if (allow_sdr) {
|
||||
char *argv_lime[] = { argv[0],
|
||||
"--sdr-soapy",
|
||||
"--sdr-device-args", "driver=lime",
|
||||
"--sdr-rx-antenna", "LNAW",
|
||||
"--sdr-tx-antenna", "BAND2",
|
||||
"--sdr-rx-gain", "25",
|
||||
"--sdr-tx-gain", "30",
|
||||
"--sdr-samplerate", "5000000",
|
||||
"--sdr-bandwidth", "15000000",
|
||||
"-s", "200000",
|
||||
};
|
||||
int argc_lime = sizeof(argv_lime) / sizeof (*argv_lime);
|
||||
return options_command_line(argc_lime, argv_lime, main_mobile_handle_options);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
#ifdef HAVE_SDR
|
||||
if (allow_sdr)
|
||||
return sdr_config_handle_options(short_option, argi, argv);
|
||||
#endif
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* global variable to quit main loop */
|
||||
int quit = 0;
|
||||
|
||||
void sighandler(int sigset)
|
||||
{
|
||||
if (sigset == SIGHUP)
|
||||
return;
|
||||
if (sigset == SIGPIPE)
|
||||
return;
|
||||
|
||||
if (clear_console_text)
|
||||
clear_console_text();
|
||||
printf("Signal received: %d\n", sigset);
|
||||
|
||||
quit = 1;
|
||||
}
|
||||
|
||||
static int get_char()
|
||||
{
|
||||
struct timeval tv = {0, 0};
|
||||
fd_set fds;
|
||||
char c = 0;
|
||||
int __attribute__((__unused__)) rc;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(0, &fds);
|
||||
select(0+1, &fds, NULL, NULL, &tv);
|
||||
if (FD_ISSET(0, &fds)) {
|
||||
rc = read(0, &c, 1);
|
||||
return c;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Loop through all transceiver instances of one network. */
|
||||
void main_mobile_loop(const char *name, int *quit, void (*myhandler)(void), const char *station_id)
|
||||
{
|
||||
int buffer_size;
|
||||
sender_t *sender;
|
||||
double last_time_call = 0, begin_time, now, sleep;
|
||||
struct termios term, term_orig;
|
||||
int c;
|
||||
int rc;
|
||||
|
||||
if (!got_init) {
|
||||
fprintf(stderr, "main_mobile_init was not called, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
/* station id preset */
|
||||
if (station_id)
|
||||
station_id = mobile_number_remove_prefix(station_id);
|
||||
|
||||
/* size of dsp buffer in samples */
|
||||
buffer_size = dsp_samplerate * dsp_buffer / 1000;
|
||||
|
||||
/* check OSMO-CC support */
|
||||
if (use_osmocc_cross && num_kanal == 1) {
|
||||
fprintf(stderr, "You selected built-in call forwarding, but only channel is used. Does this makes sense?\n");
|
||||
return;
|
||||
}
|
||||
if (use_osmocc_sock && use_osmocc_cross) {
|
||||
fprintf(stderr, "You selected OSMO-CC socket interface and built-in call forwarding, but only one can be selected.\n");
|
||||
return;
|
||||
}
|
||||
if (echo_test && call_device[0]) {
|
||||
fprintf(stderr, "You selected call device (headset) and echo test, but only one can be selected.\n");
|
||||
return;
|
||||
}
|
||||
if (use_osmocc_sock && call_device[0]) {
|
||||
fprintf(stderr, "You selected OSMO-CC socket interface, but it cannot be used with call device (headset).\n");
|
||||
return;
|
||||
}
|
||||
if (use_osmocc_cross && call_device[0]) {
|
||||
fprintf(stderr, "You selected built-in call forwarding, but it cannot be used with call device (headset).\n");
|
||||
return;
|
||||
}
|
||||
if (use_osmocc_sock && echo_test) {
|
||||
fprintf(stderr, "You selected OSMO-CC socket interface, but it cannot be used with echo test.\n");
|
||||
return;
|
||||
}
|
||||
if (use_osmocc_cross && echo_test) {
|
||||
fprintf(stderr, "You selected built-in call forwarding, but it cannot be used with echo test.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* OSMO-CC crossover */
|
||||
if (use_osmocc_cross) {
|
||||
use_osmocc_sock = 1;
|
||||
cc_argc = 0;
|
||||
cc_argv[cc_argc++] = options_strdup("local 127.0.0.1:4200");
|
||||
cc_argv[cc_argc++] = options_strdup("remote 127.0.0.1:4200");
|
||||
}
|
||||
|
||||
/* init OSMO-CC */
|
||||
if (!use_osmocc_sock)
|
||||
console_init(call_device, call_samplerate, call_buffer, loopback, echo_test, number_digits, number_lengths, station_id);
|
||||
|
||||
/* init call control instance */
|
||||
rc = call_init(name, (use_osmocc_sock) ? send_patterns : 0, release_on_disconnect, use_osmocc_sock, cc_argc, cc_argv);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to create call control instance. Quitting!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SDR
|
||||
rc = sdr_configure(dsp_samplerate);
|
||||
if (rc < 0)
|
||||
return;
|
||||
#endif
|
||||
|
||||
/* open audio */
|
||||
if (sender_open_audio(buffer_size, dsp_interval))
|
||||
return;
|
||||
if (console_open_audio(buffer_size, dsp_interval))
|
||||
return;
|
||||
|
||||
if (!loopback)
|
||||
print_aaimage();
|
||||
|
||||
/* real time priority */
|
||||
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);
|
||||
}
|
||||
|
||||
/* prepare terminal */
|
||||
tcgetattr(0, &term_orig);
|
||||
term = term_orig;
|
||||
term.c_lflag &= ~(ISIG|ICANON|ECHO);
|
||||
term.c_cc[VMIN]=1;
|
||||
term.c_cc[VTIME]=2;
|
||||
tcsetattr(0, TCSANOW, &term);
|
||||
|
||||
/* catch signals */
|
||||
signal(SIGINT, sighandler);
|
||||
signal(SIGHUP, sighandler);
|
||||
signal(SIGTERM, sighandler);
|
||||
signal(SIGPIPE, sighandler);
|
||||
|
||||
/* start streaming */
|
||||
if (sender_start_audio())
|
||||
*quit = 1;
|
||||
if (console_start_audio())
|
||||
*quit = 1;
|
||||
|
||||
while(!(*quit)) {
|
||||
begin_time = get_time();
|
||||
|
||||
/* process sound of all transceivers */
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
/* do not process audio for an audio slave, since it is done by audio master */
|
||||
if (sender->master) /* if master is set, we are an audio slave */
|
||||
continue;
|
||||
process_sender_audio(sender, quit, buffer_size);
|
||||
}
|
||||
|
||||
/* process timers */
|
||||
process_timer();
|
||||
|
||||
/* process audio for call instances */
|
||||
now = get_time();
|
||||
if (now - last_time_call >= 0.1)
|
||||
last_time_call = now;
|
||||
if (now - last_time_call >= 0.020) {
|
||||
last_time_call += 0.020;
|
||||
/* call clock every 20ms */
|
||||
call_clock();
|
||||
}
|
||||
|
||||
next_char:
|
||||
c = get_char();
|
||||
switch (c) {
|
||||
case 3:
|
||||
/* quit */
|
||||
if (clear_console_text)
|
||||
clear_console_text();
|
||||
printf("CTRL+c received, quitting!\n");
|
||||
*quit = 1;
|
||||
goto next_char;
|
||||
case 'w':
|
||||
/* toggle wave display */
|
||||
display_status_on(0);
|
||||
display_measurements_on(0);
|
||||
#ifdef HAVE_SDR
|
||||
display_iq_on(0);
|
||||
display_spectrum_on(0);
|
||||
#endif
|
||||
display_wave_on(-1);
|
||||
goto next_char;
|
||||
case 'c':
|
||||
/* toggle call state display */
|
||||
display_wave_on(0);
|
||||
display_measurements_on(0);
|
||||
#ifdef HAVE_SDR
|
||||
display_iq_on(0);
|
||||
display_spectrum_on(0);
|
||||
#endif
|
||||
display_status_on(-1);
|
||||
goto next_char;
|
||||
case 'm':
|
||||
/* toggle measurements display */
|
||||
display_wave_on(0);
|
||||
display_status_on(0);
|
||||
#ifdef HAVE_SDR
|
||||
display_iq_on(0);
|
||||
display_spectrum_on(0);
|
||||
#endif
|
||||
display_measurements_on(-1);
|
||||
goto next_char;
|
||||
#ifdef HAVE_SDR
|
||||
case 'q':
|
||||
/* toggle IQ display */
|
||||
display_wave_on(0);
|
||||
display_status_on(0);
|
||||
display_measurements_on(0);
|
||||
display_spectrum_on(0);
|
||||
display_iq_on(-1);
|
||||
goto next_char;
|
||||
case 's':
|
||||
/* toggle spectrum display */
|
||||
display_wave_on(0);
|
||||
display_status_on(0);
|
||||
display_measurements_on(0);
|
||||
display_iq_on(0);
|
||||
display_spectrum_on(-1);
|
||||
goto next_char;
|
||||
#endif
|
||||
case 'i':
|
||||
/* dump info */
|
||||
dump_info();
|
||||
goto next_char;
|
||||
#ifdef HAVE_SDR
|
||||
case 'b':
|
||||
calibrate_bias();
|
||||
goto next_char;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* process call control */
|
||||
call_media_handle();
|
||||
while (call_handle());
|
||||
if (!use_osmocc_sock)
|
||||
process_console(c);
|
||||
|
||||
if (myhandler)
|
||||
myhandler();
|
||||
|
||||
display_measurements(dsp_interval / 1000.0);
|
||||
|
||||
now = get_time();
|
||||
|
||||
/* sleep interval */
|
||||
sleep = (dsp_interval / 1000.0) - (now - begin_time);
|
||||
if (sleep > 0)
|
||||
usleep(sleep * 1000000.0);
|
||||
|
||||
// now = get_time();
|
||||
// printf("duration =%.6f\n", now - begin_time);
|
||||
}
|
||||
|
||||
/* reset signals */
|
||||
signal(SIGINT, SIG_DFL);
|
||||
signal(SIGHUP, SIG_DFL);
|
||||
signal(SIGTERM, SIG_DFL);
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
|
||||
/* get rid of last entry */
|
||||
if (clear_console_text)
|
||||
clear_console_text();
|
||||
|
||||
/* reset terminal */
|
||||
tcsetattr(0, TCSANOW, &term_orig);
|
||||
|
||||
/* reset real time prio */
|
||||
if (rt_prio > 0) {
|
||||
struct sched_param schedp;
|
||||
|
||||
memset(&schedp, 0, sizeof(schedp));
|
||||
schedp.sched_priority = 0;
|
||||
sched_setscheduler(0, SCHED_OTHER, &schedp);
|
||||
}
|
||||
|
||||
//* cleanup call control */
|
||||
call_exit();
|
||||
|
||||
/* cleanup console */
|
||||
if (!use_osmocc_sock)
|
||||
console_cleanup();
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
|
||||
extern int num_kanal;
|
||||
extern const char *kanal[];
|
||||
extern int swap_links;
|
||||
extern int num_device;
|
||||
extern int allow_sdr;
|
||||
extern int use_sdr;
|
||||
extern const char *dsp_device[];
|
||||
extern int dsp_samplerate;
|
||||
extern double dsp_interval;
|
||||
extern int dsp_buffer;
|
||||
extern int uses_emphasis;
|
||||
extern int do_pre_emphasis;
|
||||
extern int do_de_emphasis;
|
||||
extern double rx_gain;
|
||||
extern double tx_gain;
|
||||
extern int send_patterns;
|
||||
extern int loopback;
|
||||
extern int rt_prio;
|
||||
extern int fast_math;
|
||||
extern const char *write_rx_wave;
|
||||
extern const char *write_tx_wave;
|
||||
extern const char *read_rx_wave;
|
||||
extern const char *read_tx_wave;
|
||||
|
||||
struct number_lengths {
|
||||
int digits;
|
||||
const char *usage;
|
||||
};
|
||||
|
||||
const char *mobile_number_remove_prefix(const char *number);
|
||||
const char *mobile_number_check_length(const char *number);
|
||||
const char *mobile_number_check_digits(const char *number);
|
||||
extern const char *(*mobile_number_check_valid)(const char *);
|
||||
int main_mobile_number_ask(const char *number, const char *what);
|
||||
|
||||
void main_mobile_init(const char *digits, const struct number_lengths lengths[], const char *prefixes[], const char *(*check_valid)(const char *));
|
||||
void main_mobile_set_number_check_valid(const char *(*check_valid)(const char *));
|
||||
void main_mobile_print_help(const char *arg0, const char *ext_usage);
|
||||
void main_mobile_print_hotkeys(void);
|
||||
void main_mobile_print_station_id(void);
|
||||
void main_mobile_add_options(void);
|
||||
int main_mobile_handle_options(int short_option, int argi, char **argv);
|
||||
|
||||
#define OPT_ARRAY(num_name, name, value) \
|
||||
{ \
|
||||
if (num_name == MAX_SENDER) { \
|
||||
fprintf(stderr, "Too many channels defined!\n"); \
|
||||
exit(0); \
|
||||
} \
|
||||
name[num_name++] = value; \
|
||||
}
|
||||
|
||||
extern int quit;
|
||||
void sighandler(int sigset);
|
||||
|
||||
void main_mobile_loop(const char *name, int *quit, void (*myhandler)(void), const char *station_id);
|
||||
|
||||
void dump_info(void);
|
||||
|
|
@ -1,494 +0,0 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
#define CHAN sender->kanal
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "sender.h"
|
||||
#include "../libtimer/timer.h"
|
||||
#ifdef HAVE_SDR
|
||||
#include "../libsdr/sdr_config.h"
|
||||
#endif
|
||||
|
||||
/* debug time consumption of audio processing */
|
||||
//#define DEBUG_TIME_CONSUMPTION
|
||||
|
||||
sender_t *sender_head = NULL;
|
||||
static sender_t **sender_tailp = &sender_head;
|
||||
int cant_recover = 0;
|
||||
int check_channel = 1;
|
||||
|
||||
/* Init transceiver instance and link to list of transceivers. */
|
||||
int sender_create(sender_t *sender, const char *kanal, double sendefrequenz, double empfangsfrequenz, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, enum paging_signal paging_signal)
|
||||
{
|
||||
sender_t *master, *slave;
|
||||
int rc = 0;
|
||||
|
||||
sender->kanal = kanal;
|
||||
sender->sendefrequenz = sendefrequenz;
|
||||
sender->empfangsfrequenz = (loopback) ? sendefrequenz : empfangsfrequenz;
|
||||
strncpy(sender->device, device, sizeof(sender->device) - 1);
|
||||
sender->samplerate = samplerate;
|
||||
sender->rx_gain = rx_gain;
|
||||
sender->tx_gain = tx_gain;
|
||||
sender->pre_emphasis = pre_emphasis;
|
||||
sender->de_emphasis = de_emphasis;
|
||||
sender->loopback = loopback;
|
||||
sender->paging_signal = paging_signal;
|
||||
sender->write_rx_wave = write_rx_wave;
|
||||
sender->write_tx_wave = write_tx_wave;
|
||||
sender->read_rx_wave = read_rx_wave;
|
||||
sender->read_tx_wave = read_tx_wave;
|
||||
|
||||
/* no gain with SDR */
|
||||
if (use_sdr) {
|
||||
sender->rx_gain = 1.0;
|
||||
sender->tx_gain = 1.0;
|
||||
}
|
||||
|
||||
if (samplerate < 8000) {
|
||||
PDEBUG(DSENDER, DEBUG_NOTICE, "Given sample rate is below 8 KHz. Please use higher sample rate!\n");
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
PDEBUG_CHAN(DSENDER, DEBUG_DEBUG, "Creating 'Sender' instance\n");
|
||||
|
||||
/* if we find a channel that uses the same device as we do,
|
||||
* we will link us as slave to this master channel. then we
|
||||
* receive and send audio via second channel of the device
|
||||
* of the master channel.
|
||||
*/
|
||||
for (master = sender_head; master; master = master->next) {
|
||||
if (!strcmp(master->kanal, kanal)) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Channel %s may not be defined for multiple transceivers!\n", kanal);
|
||||
rc = -EIO;
|
||||
goto error;
|
||||
}
|
||||
if (check_channel && abs(atoi(master->kanal) - atoi(kanal)) == 1) {
|
||||
PDEBUG(DSENDER, DEBUG_NOTICE, "------------------------------------------------------------------------\n");
|
||||
PDEBUG(DSENDER, DEBUG_NOTICE, "NOTE: Channel %s is next to channel %s. This will cause interferences.\n", kanal, master->kanal);
|
||||
PDEBUG(DSENDER, DEBUG_NOTICE, "Please use at least one channel distance to avoid that.\n");
|
||||
PDEBUG(DSENDER, DEBUG_NOTICE, "------------------------------------------------------------------------\n");
|
||||
}
|
||||
if (!strcmp(master->device, device))
|
||||
break;
|
||||
}
|
||||
if (master) {
|
||||
if (master->paging_signal != PAGING_SIGNAL_NONE && !use_sdr) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Cannot share audio device with channel %s, because its second audio channel is used for paging signal! Use different audio device.\n", master->kanal);
|
||||
rc = -EBUSY;
|
||||
goto error;
|
||||
}
|
||||
if (paging_signal != PAGING_SIGNAL_NONE && !use_sdr) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Cannot share audio device with channel %s, because we need a second audio channel for paging signal! Use different audio device.\n", master->kanal);
|
||||
rc = -EBUSY;
|
||||
goto error;
|
||||
}
|
||||
/* link us to a master */
|
||||
sender->master = master;
|
||||
/* link master (or last slave) to us */
|
||||
for (slave = master; ; slave = slave->slave) {
|
||||
if (!slave->slave)
|
||||
break;
|
||||
}
|
||||
slave->slave = sender;
|
||||
} else {
|
||||
/* link audio device */
|
||||
#ifdef HAVE_SDR
|
||||
if (use_sdr) {
|
||||
sender->audio_open = sdr_open;
|
||||
sender->audio_start = sdr_start;
|
||||
sender->audio_close = sdr_close;
|
||||
sender->audio_read = sdr_read;
|
||||
sender->audio_write = sdr_write;
|
||||
sender->audio_get_tosend = sdr_get_tosend;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
#ifdef HAVE_ALSA
|
||||
sender->audio_open = sound_open;
|
||||
sender->audio_start = sound_start;
|
||||
sender->audio_close = sound_close;
|
||||
sender->audio_read = sound_read;
|
||||
sender->audio_write = sound_write;
|
||||
sender->audio_get_tosend = sound_get_tosend;
|
||||
#else
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "No sound card support compiled in!\n");
|
||||
rc = -ENOTSUP;
|
||||
goto error;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
rc = init_samplerate(&sender->srstate, 8000.0, (double)samplerate, 3300.0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to init sample rate conversion!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = jitter_create(&sender->dejitter, samplerate / 5);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init audio buffer!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = init_emphasis(&sender->estate, samplerate, CUT_OFF_EMPHASIS_DEFAULT, CUT_OFF_HIGHPASS_DEFAULT, CUT_OFF_LOWPASS_DEFAULT);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
|
||||
*sender_tailp = sender;
|
||||
sender_tailp = &sender->next;
|
||||
|
||||
display_wave_init(&sender->dispwav, samplerate, sender->kanal);
|
||||
display_measurements_init(&sender->dispmeas, samplerate, sender->kanal);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
sender_destroy(sender);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sender_open_audio(int buffer_size, double interval)
|
||||
{
|
||||
sender_t *master, *inst;
|
||||
int channels;
|
||||
int i;
|
||||
int rc;
|
||||
|
||||
for (master = sender_head; master; master = master->next) {
|
||||
/* skip audio slaves */
|
||||
if (master->master)
|
||||
continue;
|
||||
|
||||
/* get list of frequencies */
|
||||
channels = 0;
|
||||
for (inst = master; inst; inst = inst->slave) {
|
||||
channels++;
|
||||
}
|
||||
double tx_f[channels], rx_f[channels], paging_frequency = 0.0;
|
||||
int am[channels];
|
||||
for (i = 0, inst = master; inst; i++, inst = inst->slave) {
|
||||
tx_f[i] = inst->sendefrequenz;
|
||||
rx_f[i] = inst->empfangsfrequenz;
|
||||
am[i] = inst->am;
|
||||
if (inst->ruffrequenz)
|
||||
paging_frequency = inst->ruffrequenz;
|
||||
}
|
||||
|
||||
if (master->write_rx_wave) {
|
||||
rc = wave_create_record(&master->wave_rx_rec, master->write_rx_wave, master->samplerate, channels, (master->max_deviation) ?: 1.0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
if (master->write_tx_wave) {
|
||||
rc = wave_create_record(&master->wave_tx_rec, master->write_tx_wave, master->samplerate, channels, (master->max_deviation) ?: 1.0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
if (master->read_rx_wave) {
|
||||
rc = wave_create_playback(&master->wave_rx_play, master->read_rx_wave, &master->samplerate, &channels, (master->max_deviation) ?: 1.0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create WAVE playback instance!\n");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
if (master->read_tx_wave) {
|
||||
rc = wave_create_playback(&master->wave_tx_play, master->read_tx_wave, &master->samplerate, &channels, (master->max_deviation) ?: 1.0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create WAVE playback instance!\n");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/* open device */
|
||||
master->audio = master->audio_open(master->device, tx_f, rx_f, am, channels, paging_frequency, master->samplerate, buffer_size, interval, (master->max_deviation) ?: 1.0, master->max_modulation, master->modulation_index);
|
||||
if (!master->audio) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "No device for transceiver!\n");
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_SDR
|
||||
/* in case of initialized spectrum display (SDR), we add all channels.
|
||||
* if spectrum display was not initialized (sound card), function call is ignored */
|
||||
for (inst = sender_head; inst; inst = inst->next)
|
||||
display_spectrum_add_mark(inst->kanal, (sdr_config->swap_links) ? inst->sendefrequenz : inst->empfangsfrequenz);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sender_start_audio(void)
|
||||
{
|
||||
sender_t *master;
|
||||
int rc = 0;
|
||||
|
||||
for (master = sender_head; master; master = master->next) {
|
||||
/* skip audio slaves */
|
||||
if (master->master)
|
||||
continue;
|
||||
|
||||
rc = master->audio_start(master->audio);
|
||||
if (rc)
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Destroy transceiver instance and unlink from list. */
|
||||
void sender_destroy(sender_t *sender)
|
||||
{
|
||||
PDEBUG_CHAN(DSENDER, DEBUG_DEBUG, "Destroying 'Sender' instance\n");
|
||||
|
||||
sender_tailp = &sender_head;
|
||||
while (*sender_tailp) {
|
||||
if (sender == *sender_tailp)
|
||||
*sender_tailp = (*sender_tailp)->next;
|
||||
else
|
||||
sender_tailp = &((*sender_tailp)->next);
|
||||
}
|
||||
|
||||
if (sender->audio) {
|
||||
sender->audio_close(sender->audio);
|
||||
sender->audio = NULL;
|
||||
}
|
||||
|
||||
wave_destroy_record(&sender->wave_rx_rec);
|
||||
wave_destroy_record(&sender->wave_tx_rec);
|
||||
wave_destroy_playback(&sender->wave_rx_play);
|
||||
wave_destroy_playback(&sender->wave_tx_play);
|
||||
|
||||
jitter_destroy(&sender->dejitter);
|
||||
}
|
||||
|
||||
/* set frequency modulation and parameters */
|
||||
void sender_set_fm(sender_t *sender, double max_deviation, double max_modulation, double speech_deviation, double max_display)
|
||||
{
|
||||
sender->max_deviation = max_deviation;
|
||||
sender->max_modulation = max_modulation;
|
||||
sender->speech_deviation = speech_deviation;
|
||||
sender->max_display = max_display;
|
||||
|
||||
PDEBUG_CHAN(DSENDER, DEBUG_DEBUG, "Maximum deviation: %.1f kHz, Maximum modulation: %.1f kHz\n", max_deviation / 1000.0, max_modulation / 1000.0);
|
||||
PDEBUG_CHAN(DSENDER, DEBUG_DEBUG, "Deviation at speech level: %.1f kHz\n", speech_deviation / 1000.0);
|
||||
}
|
||||
|
||||
/* set amplitude modulation and parameters */
|
||||
void sender_set_am(sender_t *sender, double max_modulation, double speech_level, double max_display, double modulation_index)
|
||||
{
|
||||
sender->am = 1;
|
||||
sender->max_deviation = 0;
|
||||
sender->max_modulation = max_modulation;
|
||||
sender->speech_deviation = speech_level;
|
||||
sender->max_display = max_display;
|
||||
sender->modulation_index = modulation_index;
|
||||
|
||||
PDEBUG_CHAN(DSENDER, DEBUG_DEBUG, "Modulation degree: %.0f %%, Maximum modulation: %.1f kHz\n", modulation_index / 100.0, max_modulation / 1000.0);
|
||||
}
|
||||
|
||||
static void gain_samples(sample_t *samples, int length, double gain)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length; i++)
|
||||
*samples++ *= gain;
|
||||
}
|
||||
|
||||
/* Handle audio streaming of one transceiver. */
|
||||
void process_sender_audio(sender_t *sender, int *quit, int buffer_size)
|
||||
{
|
||||
sender_t *inst;
|
||||
int rc, count;
|
||||
int num_chan, i;
|
||||
#ifdef DEBUG_TIME_CONSUMPTION
|
||||
static double t1, t2, t3, t4, t5, d1 = 0, d2 = 0, d3 = 0, d4 = 0, s = 0;
|
||||
#endif
|
||||
|
||||
/* count instances for audio channel */
|
||||
for (num_chan = 0, inst = sender; inst; num_chan++, inst = inst->slave);
|
||||
sample_t buff[num_chan][buffer_size], *samples[num_chan];
|
||||
uint8_t pbuff[num_chan][buffer_size], *power[num_chan];
|
||||
enum paging_signal paging_signal[num_chan];
|
||||
int on[num_chan];
|
||||
double rf_level_db[num_chan];
|
||||
for (i = 0; i < num_chan; i++) {
|
||||
samples[i] = buff[i];
|
||||
power[i] = pbuff[i];
|
||||
}
|
||||
|
||||
#ifdef DEBUG_TIME_CONSUMPTION
|
||||
t1 = get_time();
|
||||
#endif
|
||||
count = sender->audio_get_tosend(sender->audio, buffer_size);
|
||||
if (count < 0) {
|
||||
PDEBUG_CHAN(DSENDER, DEBUG_ERROR, "Failed to get number of samples in buffer (rc = %d)!\n", count);
|
||||
if (count == -EPIPE) {
|
||||
if (cant_recover) {
|
||||
cant_recover:
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Cannot recover due to measurements, quitting!\n");
|
||||
*quit = 1;
|
||||
return;
|
||||
}
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
#ifdef DEBUG_TIME_CONSUMPTION
|
||||
t2 = get_time();
|
||||
#endif
|
||||
if (count > 0) {
|
||||
/* limit to our buffer */
|
||||
if (count > buffer_size)
|
||||
count = buffer_size;
|
||||
/* loop through all channels */
|
||||
for (i = 0, inst = sender; inst; i++, inst = inst->slave) {
|
||||
/* load TX data from audio loop or from sender instance */
|
||||
if (inst->loopback == 3)
|
||||
jitter_load(&inst->dejitter, samples[i], count);
|
||||
else
|
||||
sender_send(inst, samples[i], power[i], count);
|
||||
/* internal loopback: loop back TX audio to RX */
|
||||
if (inst->loopback == 1) {
|
||||
display_wave(&inst->dispwav, samples[i], count, inst->max_display);
|
||||
sender_receive(inst, samples[i], count, 0.0);
|
||||
}
|
||||
/* do pre emphasis towards radio */
|
||||
if (inst->pre_emphasis)
|
||||
pre_emphasis(&inst->estate, samples[i], count);
|
||||
/* tx gain */
|
||||
if (inst->tx_gain != 1.0)
|
||||
gain_samples(samples[i], count, inst->tx_gain);
|
||||
/* normal level to frequency deviation of speech level */
|
||||
gain_samples(samples[i], count, inst->speech_deviation);
|
||||
/* set paging signal */
|
||||
paging_signal[i] = inst->paging_signal;
|
||||
on[i] = inst->paging_on;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_TIME_CONSUMPTION
|
||||
t2 = get_time();
|
||||
#endif
|
||||
if (sender->wave_tx_rec.fp)
|
||||
wave_write(&sender->wave_tx_rec, samples, count);
|
||||
if (sender->wave_tx_play.fp)
|
||||
wave_read(&sender->wave_tx_play, samples, count);
|
||||
|
||||
rc = sender->audio_write(sender->audio, samples, power, count, paging_signal, on, num_chan);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to write TX data to audio device (rc = %d)\n", rc);
|
||||
if (rc == -EPIPE) {
|
||||
if (cant_recover)
|
||||
goto cant_recover;
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG_TIME_CONSUMPTION
|
||||
t3 = get_time();
|
||||
#endif
|
||||
|
||||
count = sender->audio_read(sender->audio, samples, buffer_size, num_chan, rf_level_db);
|
||||
if (count < 0) {
|
||||
/* special case when audio_read wants us to quit */
|
||||
if (count == -EPERM) {
|
||||
*quit = 1;
|
||||
return;
|
||||
}
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to read from audio device (rc = %d)!\n", count);
|
||||
if (count == -EPIPE) {
|
||||
if (cant_recover)
|
||||
goto cant_recover;
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
#ifdef DEBUG_TIME_CONSUMPTION
|
||||
t4 = get_time();
|
||||
#endif
|
||||
if (count) {
|
||||
if (sender->wave_rx_rec.fp)
|
||||
wave_write(&sender->wave_rx_rec, samples, count);
|
||||
if (sender->wave_rx_play.fp)
|
||||
wave_read(&sender->wave_rx_play, samples, count);
|
||||
|
||||
/* loop through all channels */
|
||||
for (i = 0, inst = sender; inst; i++, inst = inst->slave) {
|
||||
/* frequency deviation of speech level to normal level */
|
||||
gain_samples(samples[i], count, 1.0 / inst->speech_deviation);
|
||||
/* rx gain */
|
||||
if (inst->rx_gain != 1.0)
|
||||
gain_samples(samples[i], count, inst->rx_gain);
|
||||
/* do filter and de-emphasis from radio receive audio, process echo test */
|
||||
if (inst->de_emphasis) {
|
||||
dc_filter(&inst->estate, samples[i], count);
|
||||
de_emphasis(&inst->estate, samples[i], count);
|
||||
}
|
||||
if (inst->loopback != 1) {
|
||||
display_wave(&inst->dispwav, samples[i], count, inst->max_display);
|
||||
sender_receive(inst, samples[i], count, rf_level_db[i]);
|
||||
}
|
||||
if (inst->loopback == 3)
|
||||
jitter_save(&inst->dejitter, samples[i], count);
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG_TIME_CONSUMPTION
|
||||
t5 = get_time();
|
||||
d1 += (t2 - t1);
|
||||
d2 += (t3 - t2);
|
||||
d3 += (t4 - t3);
|
||||
d4 += (t5 - t4);
|
||||
if (get_time() - s >= 1.0) {
|
||||
printf("duration: %.3f (process TX), %.3f (send TX), %.3f (receive RX), %.3f (process RX)\n", d1, d2, d3, d4);
|
||||
s = get_time();
|
||||
d1 = d2 = d3 = d4 = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void sender_paging(sender_t *sender, int on)
|
||||
{
|
||||
sender->paging_on = on;
|
||||
}
|
||||
|
||||
sender_t *get_sender_by_empfangsfrequenz(double freq)
|
||||
{
|
||||
sender_t *sender;
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
if (sender->empfangsfrequenz == freq)
|
||||
return sender;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
#ifdef HAVE_ALSA
|
||||
#include "../libsound/sound.h"
|
||||
#endif
|
||||
#ifdef HAVE_SDR
|
||||
#include "../libsdr/sdr.h"
|
||||
#endif
|
||||
#include "../libwave/wave.h"
|
||||
#include "../libsamplerate/samplerate.h"
|
||||
#include "../libjitter/jitter.h"
|
||||
#include "../libemphasis/emphasis.h"
|
||||
#include "../libdisplay/display.h"
|
||||
|
||||
#define MAX_SENDER 16
|
||||
|
||||
/* how to send a 'paging' signal (trigger transmitter) */
|
||||
enum paging_signal {
|
||||
PAGING_SIGNAL_NONE = 0,
|
||||
PAGING_SIGNAL_TONE,
|
||||
PAGING_SIGNAL_NOTONE,
|
||||
PAGING_SIGNAL_POSITIVE,
|
||||
PAGING_SIGNAL_NEGATIVE,
|
||||
};
|
||||
|
||||
/* common structure of each transmitter */
|
||||
typedef struct sender {
|
||||
struct sender *next;
|
||||
struct sender *slave; /* points to 'slave' that uses next channel of audio device */
|
||||
struct sender *master; /* if set, the audio device is owned by 'master' */
|
||||
|
||||
/* system info */
|
||||
const char *kanal; /* channel number */
|
||||
double sendefrequenz; /* transmitter frequency */
|
||||
double empfangsfrequenz; /* receiver frequency */
|
||||
double ruffrequenz; /* special paging frequency used for B-Netz */
|
||||
|
||||
/* FM/AM levels */
|
||||
int am; /* use AM instead of FM */
|
||||
double max_deviation; /* max frequency deviation / level */
|
||||
double max_modulation; /* max frequency modulated */
|
||||
double speech_deviation; /* deviation / level of 1000 Hz reference tone at speech level */
|
||||
double modulation_index; /* AM modulation index */
|
||||
double max_display; /* level of displaying wave form */
|
||||
|
||||
/* audio */
|
||||
void *audio;
|
||||
char device[64]; /* audio device name (alsa or sdr) */
|
||||
void *(*audio_open)(const char *, double *, double *, int *, int, double, int, int, double, double, double, double);
|
||||
int (*audio_start)(void *);
|
||||
void (*audio_close)(void *);
|
||||
int (*audio_write)(void *, sample_t **, uint8_t **, int, enum paging_signal *, int *, int);
|
||||
int (*audio_read)(void *, sample_t **, int, int, double *);
|
||||
int (*audio_get_tosend)(void *, int);
|
||||
int samplerate;
|
||||
samplerate_t srstate; /* sample rate conversion state */
|
||||
double rx_gain; /* factor of level to apply on RX samples */
|
||||
double tx_gain; /* factor of level to apply on TX samples */
|
||||
int pre_emphasis; /* use pre_emhasis, done by sender */
|
||||
int de_emphasis; /* use de_emhasis, done by sender */
|
||||
emphasis_t estate; /* pre and de emphasis */
|
||||
|
||||
/* loopback test */
|
||||
int loopback; /* 0 = off, 1 = internal, 2 = external */
|
||||
|
||||
/* record and playback */
|
||||
const char *write_rx_wave; /* file name pointers */
|
||||
const char *write_tx_wave;
|
||||
const char *read_rx_wave;
|
||||
const char *read_tx_wave;
|
||||
wave_rec_t wave_rx_rec; /* wave recording (from rx) */
|
||||
wave_rec_t wave_tx_rec; /* wave recording (from tx) */
|
||||
wave_play_t wave_rx_play; /* wave playback (as rx) */
|
||||
wave_play_t wave_tx_play; /* wave playback (as tx) */
|
||||
|
||||
/* audio buffer for audio to send to transmitter (also used as loopback buffer) */
|
||||
jitter_t dejitter;
|
||||
|
||||
/* audio buffer for audio to send to caller (20ms = 160 samples @ 8000Hz) */
|
||||
sample_t rxbuf[160];
|
||||
int rxbuf_pos; /* current fill of buffer */
|
||||
|
||||
/* paging tone */
|
||||
enum paging_signal paging_signal; /* if paging signal is used and how it is performed */
|
||||
int paging_on; /* 1 or 0 for on or off */
|
||||
|
||||
/* display wave */
|
||||
dispwav_t dispwav; /* display wave form */
|
||||
|
||||
/* display measurements */
|
||||
dispmeas_t dispmeas; /* display measurements */
|
||||
} sender_t;
|
||||
|
||||
extern sender_t *sender_head;
|
||||
extern int cant_recover;
|
||||
extern int check_channel;
|
||||
|
||||
int sender_create(sender_t *sender, const char *kanal, double sendefrequenz, double empfangsfrequenz, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, enum paging_signal paging_signal);
|
||||
void sender_destroy(sender_t *sender);
|
||||
void sender_set_fm(sender_t *sender, double max_deviation, double max_modulation, double speech_deviation, double max_display);
|
||||
void sender_set_am(sender_t *sender, double max_modulation, double speech_deviation, double max_display, double modulation_index);
|
||||
int sender_open_audio(int buffer_size, double interval);
|
||||
int sender_start_audio(void);
|
||||
void process_sender_audio(sender_t *sender, int *quit, int buffer_size);
|
||||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int count);
|
||||
void sender_receive(sender_t *sender, sample_t *samples, int count, double rf_level_db);
|
||||
void sender_paging(sender_t *sender, int on);
|
||||
sender_t *get_sender_by_empfangsfrequenz(double freq);
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +0,0 @@
|
|||
|
||||
void init_testton(void);
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libmtp.a
|
||||
|
||||
libmtp_a_SOURCES = \
|
||||
mtp.c \
|
||||
layer2.c \
|
||||
layer3.c \
|
||||
crc16.c
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
#include <stdint.h>
|
||||
#include "crc16.h"
|
||||
|
||||
/*
|
||||
* 16 12 5
|
||||
* this is the CCITT CRC 16 polynomial X + X + X + 1.
|
||||
* This works out to be 0x1021, but the way the algorithm works
|
||||
* lets us use 0x8408 (the reverse of the bit pattern). The high
|
||||
* bit is always assumed to be set, thus we only use 16 bits to
|
||||
* represent the 17 bit value.
|
||||
* The low bit contains the first bit of the data on the line.
|
||||
* The low byte(bit) contains the first bit of the CRC on the line.
|
||||
*/
|
||||
|
||||
#define POLY 0x8408
|
||||
|
||||
uint16_t calc_crc16(uint8_t *data_p, int length)
|
||||
{
|
||||
int i;
|
||||
uint16_t data;
|
||||
uint16_t crc = 0xffff;
|
||||
|
||||
while (length--) {
|
||||
data = *data_p++;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if ((crc & 1) ^ (data & 1))
|
||||
crc = (crc >> 1) ^ POLY;
|
||||
else
|
||||
crc >>= 1;
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
crc = ~crc;
|
||||
|
||||
return (crc);
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
|
||||
uint16_t calc_crc16(uint8_t *data_p, int length);
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,103 +0,0 @@
|
|||
/* Jolly's implementation of MTP layer 3
|
||||
*
|
||||
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is only a minimal implementation to make a C-Netz-BTS working.
|
||||
*/
|
||||
|
||||
#define CHAN mtp->name
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include "../libtimer/timer.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "mtp.h"
|
||||
|
||||
/* message from layer 4 */
|
||||
int mtp_send(mtp_t *mtp, enum mtp_prim prim, uint8_t slc, uint8_t *data, int len)
|
||||
{
|
||||
uint8_t buffer[len + 4];
|
||||
|
||||
if (prim == MTP_PRIM_DATA) {
|
||||
PDEBUG_CHAN(DMTP3, DEBUG_DEBUG, "Send frame to remote: SIO=0x%02x DPC=%d OPC=%d SLC=%d %s\n", mtp->sio, mtp->remote_pc, mtp->local_pc, slc, debug_hex(data, len));
|
||||
/* add header */
|
||||
buffer[0] = mtp->remote_pc;
|
||||
buffer[1] = (mtp->remote_pc >> 8) & 0x3f;
|
||||
buffer[1] |= (mtp->local_pc << 6) & 0xc0;
|
||||
buffer[2] = mtp->local_pc >> 2;
|
||||
buffer[3] = (mtp->local_pc >> 10) & 0x0f;
|
||||
buffer[3] |= slc << 4;
|
||||
|
||||
/* add payload */
|
||||
if (len)
|
||||
memcpy(buffer + 4, data, len);
|
||||
data = buffer;
|
||||
len += 4;
|
||||
}
|
||||
|
||||
/* transmit */
|
||||
return mtp_l3l2(mtp, prim, mtp->sio, data, len);
|
||||
}
|
||||
|
||||
/* message from layer 2 */
|
||||
void mtp_l2l3(mtp_t *mtp, enum mtp_prim prim, uint8_t sio, uint8_t *data, int len)
|
||||
{
|
||||
uint16_t dpc, opc;
|
||||
uint8_t slc = 0;
|
||||
|
||||
if (prim == MTP_PRIM_DATA) {
|
||||
if (len < 4) {
|
||||
PDEBUG_CHAN(DMTP3, DEBUG_NOTICE, "Short frame from layer 2 (len=%d)\n", len);
|
||||
return;
|
||||
}
|
||||
|
||||
/* parse header */
|
||||
dpc = data[0];
|
||||
dpc |= (data[1] << 8) & 0x3f00;
|
||||
opc = data[1] >> 6;
|
||||
opc |= data[2] << 2;
|
||||
opc |= (data[3] << 10) & 0x3c00;
|
||||
slc = data[3] >> 4;
|
||||
data += 4;
|
||||
len -= 4;
|
||||
|
||||
PDEBUG_CHAN(DMTP3, DEBUG_DEBUG, "Received frame from remote: SIO=0x%02x DPC=%d OPC=%d SLC=%d %s\n", sio, dpc, opc, slc, debug_hex(data, len));
|
||||
|
||||
if (dpc != mtp->local_pc || opc != mtp->remote_pc) {
|
||||
PDEBUG_CHAN(DMTP3, DEBUG_NOTICE, "Received message with wrong point codes: %d->%d but expecting %d->%d\n", opc, dpc, mtp->remote_pc, mtp->local_pc);
|
||||
return;
|
||||
}
|
||||
if ((sio & 0x0f) == 0x0 && len >= 1) {
|
||||
PDEBUG_CHAN(DMTP3, DEBUG_NOTICE, "MGMT message received: SLC=%d H0=%d H1=%d %s\n", slc, data[0] & 0xf, data[0] >> 4, debug_hex(data + 1, len - 1));
|
||||
return;
|
||||
}
|
||||
if (sio != mtp->sio) {
|
||||
PDEBUG_CHAN(DMTP3, DEBUG_NOTICE, "Received message with wrong SIO: 0x%02x but expecting 0x%02x\n", sio, mtp->sio);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* receive */
|
||||
mtp->mtp_receive(mtp->inst, prim, slc, data, len);
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
/* MTP common functions
|
||||
*
|
||||
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define CHAN mtp->name
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "../libtimer/timer.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "mtp.h"
|
||||
|
||||
static void mtp_t1(struct timer *timer)
|
||||
{
|
||||
mtp_t *mtp = (mtp_t *)timer->priv;
|
||||
|
||||
mtp_send(mtp, MTP_PRIM_T1_TIMEOUT, 0, NULL, 0);
|
||||
}
|
||||
|
||||
static void mtp_t2(struct timer *timer)
|
||||
{
|
||||
mtp_t *mtp = (mtp_t *)timer->priv;
|
||||
|
||||
mtp_send(mtp, MTP_PRIM_T2_TIMEOUT, 0, NULL, 0);
|
||||
}
|
||||
|
||||
static void mtp_t3(struct timer *timer)
|
||||
{
|
||||
mtp_t *mtp = (mtp_t *)timer->priv;
|
||||
|
||||
mtp_send(mtp, MTP_PRIM_T3_TIMEOUT, 0, NULL, 0);
|
||||
}
|
||||
|
||||
static void mtp_t4(struct timer *timer)
|
||||
{
|
||||
mtp_t *mtp = (mtp_t *)timer->priv;
|
||||
|
||||
mtp_send(mtp, MTP_PRIM_T4_TIMEOUT, 0, NULL, 0);
|
||||
}
|
||||
|
||||
int mtp_init(mtp_t *mtp, const char *name, void *inst, void (*mtp_receive)(void *inst, enum mtp_prim prim, uint8_t slc, uint8_t *data, int len), int bitrate, int ignore_monitor, uint8_t sio, uint16_t local_pc, uint16_t remote_pc)
|
||||
{
|
||||
memset(mtp, 0, sizeof(*mtp));
|
||||
|
||||
mtp->name = name;
|
||||
mtp->inst = inst;
|
||||
mtp->mtp_receive = mtp_receive;
|
||||
if (bitrate != 64000 && bitrate != 4800) {
|
||||
fprintf(stderr, "Wrong bit rate %d, please fix!\n", bitrate);
|
||||
abort();
|
||||
}
|
||||
mtp->bitrate = bitrate;
|
||||
mtp->ignore_monitor = ignore_monitor;
|
||||
mtp->sio = sio;
|
||||
mtp->local_pc = local_pc;
|
||||
mtp->remote_pc = remote_pc;
|
||||
timer_init(&mtp->t1, mtp_t1, mtp);
|
||||
timer_init(&mtp->t2, mtp_t2, mtp);
|
||||
timer_init(&mtp->t3, mtp_t3, mtp);
|
||||
timer_init(&mtp->t4, mtp_t4, mtp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mtp_exit(mtp_t *mtp)
|
||||
{
|
||||
if (!mtp)
|
||||
return;
|
||||
|
||||
timer_exit(&mtp->t1);
|
||||
timer_exit(&mtp->t2);
|
||||
timer_exit(&mtp->t3);
|
||||
timer_exit(&mtp->t4);
|
||||
|
||||
mtp_flush(mtp);
|
||||
}
|
||||
|
||||
void mtp_flush(mtp_t *mtp)
|
||||
{
|
||||
struct mtp_msg *temp;
|
||||
|
||||
while (mtp->tx_queue) {
|
||||
temp = mtp->tx_queue;
|
||||
mtp->tx_queue = mtp->tx_queue->next;
|
||||
free(temp);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
|
||||
enum mtp_prim {
|
||||
MTP_PRIM_POWER_ON,
|
||||
MTP_PRIM_EMERGENCY,
|
||||
MTP_PRIM_EMERGENCY_CEASES,
|
||||
MTP_PRIM_LOCAL_PROCESSOR_OUTAGE,
|
||||
MTP_PRIM_LOCAL_PROCESSOR_RECOVERED,
|
||||
MTP_PRIM_REMOTE_PROCESSOR_OUTAGE,
|
||||
MTP_PRIM_REMOTE_PROCESSOR_RECOVERED,
|
||||
MTP_PRIM_START,
|
||||
MTP_PRIM_STOP,
|
||||
MTP_PRIM_DATA,
|
||||
MTP_PRIM_IN_SERVICE,
|
||||
MTP_PRIM_OUT_OF_SERVICE,
|
||||
MTP_PRIM_SIOS,
|
||||
MTP_PRIM_SIO,
|
||||
MTP_PRIM_SIN,
|
||||
MTP_PRIM_SIE,
|
||||
MTP_PRIM_SIPO,
|
||||
MTP_PRIM_SIB,
|
||||
MTP_PRIM_MSU,
|
||||
MTP_PRIM_FISU,
|
||||
MTP_PRIM_T1_TIMEOUT,
|
||||
MTP_PRIM_T2_TIMEOUT,
|
||||
MTP_PRIM_T3_TIMEOUT,
|
||||
MTP_PRIM_T4_TIMEOUT,
|
||||
MTP_PRIM_CORRECT_SU,
|
||||
MTP_PRIM_ABORT_PROVING,
|
||||
MTP_PRIM_LINK_FAILURE,
|
||||
};
|
||||
|
||||
#define MTP_CAUSE_ALIGNMENT_TIMEOUT 1
|
||||
#define MTP_CAUSE_LINK_FAILURE_LOCAL 2
|
||||
#define MTP_CAUSE_LINK_FAILURE_REMOTE 3
|
||||
#define MTP_CAUSE_PROVING_FAILURE_LOCAL 4
|
||||
#define MTP_CAUSE_PROVING_FAILURE_REMOTE 5
|
||||
#define MTP_CAUSE_PROVING_TIMEOUT 6
|
||||
|
||||
enum mtp_l2state {
|
||||
MTP_L2STATE_POWER_OFF = 0,
|
||||
MTP_L2STATE_OUT_OF_SERVICE,
|
||||
MTP_L2STATE_NOT_ALIGNED,
|
||||
MTP_L2STATE_ALIGNED,
|
||||
MTP_L2STATE_PROVING,
|
||||
MTP_L2STATE_ALIGNED_READY,
|
||||
MTP_L2STATE_ALIGNED_NOT_READY,
|
||||
MTP_L2STATE_IN_SERVICE,
|
||||
MTP_L2STATE_PROCESSOR_OUTAGE,
|
||||
};
|
||||
|
||||
struct mtp_msg {
|
||||
struct mtp_msg *next;
|
||||
uint8_t sequence;
|
||||
int transmitted;
|
||||
int len;
|
||||
uint8_t sio;
|
||||
uint8_t data[0];
|
||||
};
|
||||
|
||||
typedef struct mtp {
|
||||
/* config */
|
||||
const char *name; /* instance name (channel) */
|
||||
int bitrate; /* link bit rate (4.8k or 64k) */
|
||||
int ignore_monitor; /* ignore link monitoring errors */
|
||||
|
||||
/* layer 2 states */
|
||||
enum mtp_l2state l2_state; /* layer 2 state (link & alignment state) */
|
||||
int local_emergency; /* we request emergency alignment */
|
||||
int remote_emergency; /* remote requests emergency alignment */
|
||||
int local_outage; /* current local processor outage */
|
||||
int remote_outage; /* current remote processor outage */
|
||||
int tx_lssu; /* what LSSU status to transmit (-1 for nothing) */
|
||||
struct timer t1; /* timer "alignment ready" */
|
||||
struct timer t2; /* timer "not aligned" */
|
||||
struct timer t3; /* timer "aligned" */
|
||||
struct timer t4; /* proving period timer */
|
||||
int proving_try; /* counts number of proving attempts */
|
||||
int further_proving;/* flag that indicates another proving attempt */
|
||||
|
||||
/* frame transmission */
|
||||
uint8_t tx_frame[272]; /* frame memory */
|
||||
int tx_frame_len; /* number of bytes in frame */
|
||||
int tx_byte_count; /* count bytes within frame */
|
||||
int tx_bit_count; /* count bits within byte */
|
||||
int tx_transmitting;/* transmit frame, if 0: transmit flag */
|
||||
uint8_t tx_byte; /* current byte transmitting */
|
||||
uint8_t tx_stream; /* output stream to track bit stuffing */
|
||||
|
||||
/* frame reception */
|
||||
uint8_t rx_frame[272]; /* frame memory */
|
||||
int rx_byte_count; /* count bytes within frame */
|
||||
int rx_bit_count; /* count bits within byte */
|
||||
int rx_receiving; /* receive frame, if 0: no flag yet */
|
||||
uint8_t rx_byte; /* current byte receiving */
|
||||
uint8_t rx_stream; /* input stream to track bit stuffing/flag/abort */
|
||||
int rx_flag_count; /* counter to detect exessively received flags */
|
||||
int rx_octet_counting; /* we are in octet counting mode */
|
||||
int rx_octet_count; /* counter when performing octet counting */
|
||||
|
||||
/* frame sequencing */
|
||||
struct mtp_msg *tx_queue; /* head of all messages in queue */
|
||||
uint8_t tx_queue_seq; /* last sequence assigned to a frame in the queue */
|
||||
uint8_t tx_seq; /* current sequence number transmitting */
|
||||
uint8_t fib; /* current FIB */
|
||||
uint8_t rx_seq; /* last accepted seqeuence number */
|
||||
uint8_t bib; /* current BIB */
|
||||
int tx_nack; /* next frame shall send a NAK by inverting BIB */
|
||||
|
||||
/* monitor */
|
||||
int proving_errors;/* counts errors while proving */
|
||||
int monitor_errors;/* counts link errors */
|
||||
int monitor_good; /* counts good frames */
|
||||
|
||||
|
||||
/* layer 3 */
|
||||
void (*mtp_receive)(void *inst, enum mtp_prim prim, uint8_t slc, uint8_t *data, int len);
|
||||
void *inst;
|
||||
uint8_t sio;
|
||||
uint16_t local_pc, remote_pc;
|
||||
} mtp_t;
|
||||
|
||||
int mtp_init(mtp_t *mtp, const char *name, void *inst, void (*mtp_receive)(void *inst, enum mtp_prim prim, uint8_t slc, uint8_t *data, int len), int bitrate, int ignore_monitor, uint8_t sio, uint16_t local_pc, uint16_t remote_pc);
|
||||
void mtp_exit(mtp_t *mtp);
|
||||
void mtp_flush(mtp_t *mtp);
|
||||
|
||||
void mtp_l2_new_state(mtp_t *mtp, enum mtp_l2state state);
|
||||
|
||||
int mtp_send(mtp_t *mtp, enum mtp_prim prim, uint8_t slc, uint8_t *data, int len);
|
||||
int mtp_l3l2(mtp_t *mtp, enum mtp_prim prim, uint8_t sio, uint8_t *data, int len);
|
||||
void mtp_l2l3(mtp_t *mtp, enum mtp_prim prim, uint8_t sio, uint8_t *data, int len);
|
||||
|
||||
uint8_t mtp_send_bit(mtp_t *mtp);
|
||||
void mtp_receive_bit(mtp_t *mtp, uint8_t bit);
|
||||
|
||||
void mtp_send_block(mtp_t *mtp, uint8_t *data, int len);
|
||||
void mtp_receive_block(mtp_t *mtp, uint8_t *data, int len);
|
||||
|
||||
/* overload receive functions to redirect for sniffing */
|
||||
extern void (*func_mtp_receive_lssu)(mtp_t *mtp, uint8_t fsn, uint8_t bib, uint8_t status);
|
||||
extern void (*func_mtp_receive_msu)(mtp_t *mtp, uint8_t bsn, uint8_t bib, uint8_t fsn, uint8_t fib, uint8_t sio, uint8_t *data, int len);
|
||||
extern void (*func_mtp_receive_fisu)(mtp_t *mtp, uint8_t bsn, uint8_t bib, uint8_t fsn, uint8_t fib);
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = liboptions.a
|
||||
|
||||
liboptions_a_SOURCES = \
|
||||
options.c
|
||||
|
|
@ -1,339 +0,0 @@
|
|||
/* command line options and config file parsing
|
||||
*
|
||||
* (C) 2018 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 <errno.h>
|
||||
#include "options.h"
|
||||
#include "../libdebug/debug.h"
|
||||
|
||||
typedef struct option {
|
||||
struct option *next;
|
||||
int short_option;
|
||||
const char *long_option;
|
||||
int parameter_count;
|
||||
} option_t;
|
||||
|
||||
static option_t *option_head = NULL;
|
||||
static option_t **option_tailp = &option_head;
|
||||
static int first_option = 1;
|
||||
|
||||
static struct options_strdup_entry {
|
||||
struct options_strdup_entry *next;
|
||||
char s[1];
|
||||
} *options_strdup_list = NULL;
|
||||
|
||||
char *options_strdup(const char *s)
|
||||
{
|
||||
struct options_strdup_entry *o;
|
||||
|
||||
o = malloc(sizeof(struct options_strdup_entry) + strlen(s));
|
||||
if (!o) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "No mem!\n");
|
||||
abort();
|
||||
}
|
||||
o->next = options_strdup_list;
|
||||
options_strdup_list = o;
|
||||
strcpy(o->s, s);
|
||||
|
||||
return o->s;
|
||||
}
|
||||
|
||||
void option_add(int short_option, const char *long_option, int parameter_count)
|
||||
{
|
||||
option_t *option;
|
||||
|
||||
/* check if option already exists or is not allowed */
|
||||
for (option = option_head; option; option = option->next) {
|
||||
if (!strcmp(option->long_option, "config")) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "Option '%s' is not allowed to add, please fix!\n", option->long_option);
|
||||
abort();
|
||||
}
|
||||
if (option->short_option == short_option
|
||||
|| !strcmp(option->long_option, long_option)) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "Option '%s' added twice, please fix!\n", option->long_option);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
option = calloc(1, sizeof(*option));
|
||||
if (!option) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "No mem!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
option->short_option = short_option;
|
||||
option->long_option = long_option;
|
||||
option->parameter_count = parameter_count;
|
||||
*option_tailp = option;
|
||||
option_tailp = &(option->next);
|
||||
}
|
||||
|
||||
int options_config_file(int argc, char *argv[], const char *config_file, int (*handle_options)(int short_option, int argi, char *argv[]))
|
||||
{
|
||||
static const char *home;
|
||||
char config[256];
|
||||
FILE *fp;
|
||||
char buffer[256], opt[256], param[256], *p, *args[16];
|
||||
char params[1024];
|
||||
int line;
|
||||
int rc = 1;
|
||||
int i, j, quote;
|
||||
option_t *option;
|
||||
|
||||
/* select for alternative config file */
|
||||
if (argc > 2 && !strcmp(argv[1], "--config"))
|
||||
config_file = argv[2];
|
||||
|
||||
/* add home directory */
|
||||
if (config_file[0] == '~' && config_file[1] == '/') {
|
||||
home = getenv("HOME");
|
||||
if (home == NULL)
|
||||
return 1;
|
||||
sprintf(config, "%s/%s", home, config_file + 2);
|
||||
} else
|
||||
strcpy(config, config_file);
|
||||
|
||||
/* open config file */
|
||||
fp = fopen(config, "r");
|
||||
if (!fp) {
|
||||
PDEBUG(DOPTIONS, DEBUG_INFO, "Config file '%s' seems not to exist, using command line options only.\n", config);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* parse config file */
|
||||
line = 0;
|
||||
while((fgets(buffer, sizeof(buffer), fp))) {
|
||||
line++;
|
||||
/* prevent buffer overflow */
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
/* cut away new-line and white spaces */
|
||||
while (buffer[0] && buffer[strlen(buffer) - 1] <= ' ')
|
||||
buffer[strlen(buffer) - 1] = '\0';
|
||||
p = buffer;
|
||||
/* remove white spaces in front of first keyword */
|
||||
while (*p > '\0' && *p <= ' ')
|
||||
p++;
|
||||
/* ignore '#' lines */
|
||||
if (*p == '#')
|
||||
continue;
|
||||
/* get option form line */
|
||||
i = 0;
|
||||
while (*p > ' ')
|
||||
opt[i++] = *p++;
|
||||
opt[i] = '\0';
|
||||
if (opt[0] == '\0')
|
||||
continue;
|
||||
/* skip white spaces behind option */
|
||||
while (*p > '\0' && *p <= ' ')
|
||||
p++;
|
||||
/* get param from line */
|
||||
params[0] = '\0';
|
||||
i = 0;
|
||||
while (*p) {
|
||||
/* copy parameter */
|
||||
j = 0;
|
||||
quote = 0;
|
||||
while (*p) {
|
||||
/* escape allows all following characters */
|
||||
if (*p == '\\') {
|
||||
p++;
|
||||
if (*p)
|
||||
param[j++] = *p++;
|
||||
continue;
|
||||
}
|
||||
/* no quote, check for them or break on white space */
|
||||
if (quote == 0) {
|
||||
if (*p == '\'') {
|
||||
quote = 1;
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
if (*p == '\"') {
|
||||
quote = 2;
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
if (*p <= ' ')
|
||||
break;
|
||||
}
|
||||
/* single quote, check for unquote */
|
||||
if (quote == 1 && *p == '\'') {
|
||||
quote = 0;
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
/* double quote, check for unquote */
|
||||
if (quote == 2 && *p == '\"') {
|
||||
quote = 0;
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
/* copy character */
|
||||
param[j++] = *p++;
|
||||
}
|
||||
param[j] = '\0';
|
||||
args[i] = options_strdup(param);
|
||||
sprintf(strchr(params, '\0'), " '%s'", param);
|
||||
/* skip white spaces behind option */
|
||||
while (*p > '\0' && *p <= ' ')
|
||||
p++;
|
||||
i++;
|
||||
}
|
||||
/* search option */
|
||||
for (option = option_head; option; option = option->next) {
|
||||
if (opt[0] == option->short_option && opt[1] == '\0') {
|
||||
PDEBUG(DOPTIONS, DEBUG_INFO, "Config file option '%s' ('%s'), parameter%s\n", opt, option->long_option, params);
|
||||
break;
|
||||
}
|
||||
if (!strcmp(opt, option->long_option)) {
|
||||
PDEBUG(DOPTIONS, DEBUG_INFO, "Config file option '%s', parameter%s\n", opt, params);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!option) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "Given option '%s' in config file '%s' at line %d is not a valid option, use '-h' for help!\n", opt, config_file, line);
|
||||
rc = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
if (option->parameter_count != i) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "Given option '%s' in config file '%s' at line %d requires %d parameter(s), use '-h' for help!\n", opt, config_file, line, option->parameter_count);
|
||||
return -EINVAL;
|
||||
}
|
||||
rc = handle_options(option->short_option, 0, args);
|
||||
if (rc <= 0)
|
||||
goto done;
|
||||
first_option = 0;
|
||||
}
|
||||
|
||||
done:
|
||||
/* close config file */
|
||||
fclose(fp);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int options_command_line(int argc, char *argv[], int (*handle_options)(int short_option, int argi, char *argv[]))
|
||||
{
|
||||
option_t *option;
|
||||
char params[1024];
|
||||
int argi, i;
|
||||
int rc;
|
||||
|
||||
for (argi = 1; argi < argc; argi++) {
|
||||
/* --config */
|
||||
if (!strcmp(argv[argi], "--config")) {
|
||||
if (argi > 1) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' must be the first option specified, use '-h' for help!\n", argv[argi]);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (argc <= 2) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' requires 1 parameter, use '-h' for help!\n", argv[argi]);
|
||||
return -EINVAL;
|
||||
}
|
||||
argi += 1;
|
||||
continue;
|
||||
}
|
||||
if (argv[argi][0] == '-') {
|
||||
if (argv[argi][1] != '-') {
|
||||
if (strlen(argv[argi]) != 2) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' exceeds one character, use '-h' for help!\n", argv[argi]);
|
||||
return -EINVAL;
|
||||
}
|
||||
/* -x */
|
||||
for (option = option_head; option; option = option->next) {
|
||||
if (argv[argi][1] == option->short_option) {
|
||||
if (option->parameter_count && argi + option->parameter_count < argc) {
|
||||
params[0] = '\0';
|
||||
for (i = 0; i < option->parameter_count; i++)
|
||||
sprintf(strchr(params, '\0'), " '%s'", argv[argi + 1 + i]);
|
||||
PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s' ('--%s'), parameter%s\n", argv[argi], option->long_option, params);
|
||||
} else
|
||||
PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s' ('--%s')\n", argv[argi], option->long_option);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* --xxxxxx */
|
||||
for (option = option_head; option; option = option->next) {
|
||||
if (!strcmp(argv[argi] + 2, option->long_option)) {
|
||||
if (option->parameter_count && argi + option->parameter_count < argc) {
|
||||
params[0] = '\0';
|
||||
for (i = 0; i < option->parameter_count; i++)
|
||||
sprintf(strchr(params, '\0'), " '%s'", argv[argi + 1 + i]);
|
||||
PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s', parameter%s\n", argv[argi], params);
|
||||
} else
|
||||
PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s'\n", argv[argi]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!option) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' is not a valid option, use '-h' for help!\n", argv[argi]);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (argi + option->parameter_count >= argc) {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' requires %d parameter(s), use '-h' for help!\n", argv[argi], option->parameter_count);
|
||||
return -EINVAL;
|
||||
}
|
||||
rc = handle_options(option->short_option, argi + 1, argv);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
first_option = 0;
|
||||
argi += option->parameter_count;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
/* no more options, so we check if there is an option after a non-option parameter */
|
||||
for (i = argi; i < argc; i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' behind command line parameter '%s' not allowed! Please put all command line options before command line parameter(s).\n", argv[i], argv[argi]);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return argi;
|
||||
}
|
||||
|
||||
int option_is_first(void)
|
||||
{
|
||||
return first_option;
|
||||
}
|
||||
|
||||
void options_free(void)
|
||||
{
|
||||
while (options_strdup_list) {
|
||||
struct options_strdup_entry *o;
|
||||
o = options_strdup_list;
|
||||
options_strdup_list = o->next;
|
||||
free(o);
|
||||
}
|
||||
|
||||
while (option_head) {
|
||||
option_t *o;
|
||||
o = option_head;
|
||||
option_head = o->next;
|
||||
free(o);
|
||||
}
|
||||
option_tailp = &option_head;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
|
||||
char *options_strdup(const char *s);
|
||||
void option_add(int short_option, const char *long_option, int parameter_count);
|
||||
int options_config_file(int argc, char *argv[], const char *config_file, int (*handle_options)(int short_option, int argi, char *argv[]));
|
||||
int options_command_line(int argc, char *argv[], int (*handle_options)(int short_option, int argi, char *argv[]));
|
||||
int option_is_first(void);
|
||||
void options_free(void);
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libosmocc.a
|
||||
|
||||
libosmocc_a_SOURCES = \
|
||||
message.c \
|
||||
socket.c \
|
||||
cause.c \
|
||||
screen.c \
|
||||
endpoint.c \
|
||||
session.c \
|
||||
sdp.c \
|
||||
rtp.c \
|
||||
helper.c
|
||||
|
|
@ -1,252 +0,0 @@
|
|||
/* OSMO-CC Processing: convert causes
|
||||
*
|
||||
* (C) 2019 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 <arpa/inet.h>
|
||||
#include "message.h"
|
||||
#include "cause.h"
|
||||
|
||||
/* stolen from freeswitch */
|
||||
/* map sip responses to QSIG cause codes ala RFC4497 section 8.4.4 */
|
||||
static uint8_t status2isdn_cause(uint16_t status)
|
||||
{
|
||||
switch (status) {
|
||||
case 200:
|
||||
return 16; //SWITCH_CAUSE_NORMAL_CLEARING;
|
||||
case 401:
|
||||
case 402:
|
||||
case 403:
|
||||
case 407:
|
||||
case 603:
|
||||
return 21; //SWITCH_CAUSE_CALL_REJECTED;
|
||||
case 404:
|
||||
return 1; //SWITCH_CAUSE_UNALLOCATED_NUMBER;
|
||||
case 485:
|
||||
case 604:
|
||||
return 3; //SWITCH_CAUSE_NO_ROUTE_DESTINATION;
|
||||
case 408:
|
||||
case 504:
|
||||
return 102; //SWITCH_CAUSE_RECOVERY_ON_TIMER_EXPIRE;
|
||||
case 410:
|
||||
return 22; //SWITCH_CAUSE_NUMBER_CHANGED;
|
||||
case 413:
|
||||
case 414:
|
||||
case 416:
|
||||
case 420:
|
||||
case 421:
|
||||
case 423:
|
||||
case 505:
|
||||
case 513:
|
||||
return 127; //SWITCH_CAUSE_INTERWORKING;
|
||||
case 480:
|
||||
return 180; //SWITCH_CAUSE_NO_USER_RESPONSE;
|
||||
case 400:
|
||||
case 481:
|
||||
case 500:
|
||||
case 503:
|
||||
return 41; //SWITCH_CAUSE_NORMAL_TEMPORARY_FAILURE;
|
||||
case 486:
|
||||
case 600:
|
||||
return 17; //SWITCH_CAUSE_USER_BUSY;
|
||||
case 484:
|
||||
return 28; //SWITCH_CAUSE_INVALID_NUMBER_FORMAT;
|
||||
case 488:
|
||||
case 606:
|
||||
return 88; //SWITCH_CAUSE_INCOMPATIBLE_DESTINATION;
|
||||
case 502:
|
||||
return 38; //SWITCH_CAUSE_NETWORK_OUT_OF_ORDER;
|
||||
case 405:
|
||||
return 63; //SWITCH_CAUSE_SERVICE_UNAVAILABLE;
|
||||
case 406:
|
||||
case 415:
|
||||
case 501:
|
||||
return 79; //SWITCH_CAUSE_SERVICE_NOT_IMPLEMENTED;
|
||||
case 482:
|
||||
case 483:
|
||||
return 25; //SWITCH_CAUSE_EXCHANGE_ROUTING_ERROR;
|
||||
case 487:
|
||||
return 31; //??? SWITCH_CAUSE_ORIGINATOR_CANCEL;
|
||||
default:
|
||||
return 31; //SWITCH_CAUSE_NORMAL_UNSPECIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t isdn2status_cause(uint8_t cause, uint8_t location)
|
||||
{
|
||||
switch (cause) {
|
||||
case 1:
|
||||
return 404;
|
||||
case 2:
|
||||
return 404;
|
||||
case 3:
|
||||
return 404;
|
||||
case 17:
|
||||
return 486;
|
||||
case 18:
|
||||
return 408;
|
||||
case 19:
|
||||
return 480;
|
||||
case 20:
|
||||
return 480;
|
||||
case 21:
|
||||
if (location == OSMO_CC_LOCATION_USER)
|
||||
return 603;
|
||||
return 403;
|
||||
case 22:
|
||||
//return 301;
|
||||
return 410;
|
||||
case 23:
|
||||
return 410;
|
||||
case 26:
|
||||
return 404;
|
||||
case 27:
|
||||
return 502;
|
||||
case 28:
|
||||
return 484;
|
||||
case 29:
|
||||
return 501;
|
||||
case 31:
|
||||
return 480;
|
||||
case 34:
|
||||
return 503;
|
||||
case 38:
|
||||
return 503;
|
||||
case 41:
|
||||
return 503;
|
||||
case 42:
|
||||
return 503;
|
||||
case 47:
|
||||
return 503;
|
||||
case 55:
|
||||
return 403;
|
||||
case 57:
|
||||
return 403;
|
||||
case 58:
|
||||
return 503;
|
||||
case 65:
|
||||
return 488;
|
||||
case 69:
|
||||
return 501;
|
||||
case 70:
|
||||
return 488;
|
||||
case 79:
|
||||
return 501;
|
||||
case 87:
|
||||
return 403;
|
||||
case 88:
|
||||
return 503;
|
||||
case 102:
|
||||
return 504;
|
||||
case 111:
|
||||
return 500;
|
||||
case 127:
|
||||
return 500;
|
||||
default:
|
||||
return 468;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t socket2isdn_cause(uint8_t sock)
|
||||
{
|
||||
switch (sock) {
|
||||
case OSMO_CC_SOCKET_CAUSE_FAILED:
|
||||
return 47;
|
||||
case OSMO_CC_SOCKET_CAUSE_BROKEN_PIPE:
|
||||
return 41;
|
||||
case OSMO_CC_SOCKET_CAUSE_VERSION_MISMATCH:
|
||||
return 38;
|
||||
case OSMO_CC_SOCKET_CAUSE_TIMEOUT:
|
||||
return 41;
|
||||
default:
|
||||
return 31;
|
||||
}
|
||||
}
|
||||
|
||||
void osmo_cc_convert_cause(struct osmo_cc_ie_cause *cause)
|
||||
{
|
||||
/* complete cause, from socket cause */
|
||||
if (cause->socket_cause && cause->isdn_cause == 0 && ntohs(cause->sip_cause_networkorder) == 0)
|
||||
cause->isdn_cause = socket2isdn_cause(cause->socket_cause);
|
||||
|
||||
/* convert ISDN cause to SIP cause */
|
||||
if (cause->isdn_cause && ntohs(cause->sip_cause_networkorder) == 0) {
|
||||
cause->sip_cause_networkorder = htons(isdn2status_cause(cause->isdn_cause, cause->location));
|
||||
}
|
||||
|
||||
/* convert SIP cause to ISDN cause */
|
||||
if (ntohs(cause->sip_cause_networkorder) && cause->isdn_cause == 0) {
|
||||
cause->isdn_cause = status2isdn_cause(ntohs(cause->sip_cause_networkorder));
|
||||
}
|
||||
|
||||
/* no cause at all: use Normal Call Clearing */
|
||||
if (cause->isdn_cause == 0 && ntohs(cause->sip_cause_networkorder) == 0) {
|
||||
cause->isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR;
|
||||
cause->sip_cause_networkorder = htons(486);
|
||||
}
|
||||
}
|
||||
|
||||
void osmo_cc_convert_cause_msg(osmo_cc_msg_t *msg)
|
||||
{
|
||||
void *ie;
|
||||
uint8_t type;
|
||||
uint16_t length;
|
||||
void *value;
|
||||
|
||||
/* search for (all) cause IE and convert the values, if needed */
|
||||
ie = msg->data;
|
||||
while ((value = osmo_cc_msg_sep_ie(msg, &ie, &type, &length))) {
|
||||
if (type == OSMO_CC_IE_CAUSE && length >= sizeof(struct osmo_cc_ie_cause)) {
|
||||
osmo_cc_convert_cause(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t osmo_cc_collect_cause(uint8_t old_cause, uint8_t new_cause)
|
||||
{
|
||||
/* first cause */
|
||||
if (old_cause == 0)
|
||||
return new_cause;
|
||||
|
||||
/* first prio: return 17 */
|
||||
if (old_cause == OSMO_CC_ISDN_CAUSE_USER_BUSY
|
||||
|| new_cause == OSMO_CC_ISDN_CAUSE_USER_BUSY)
|
||||
return OSMO_CC_ISDN_CAUSE_USER_BUSY;
|
||||
|
||||
/* second prio: return 21 */
|
||||
if (old_cause == OSMO_CC_ISDN_CAUSE_CALL_REJECTED
|
||||
|| new_cause == OSMO_CC_ISDN_CAUSE_CALL_REJECTED)
|
||||
return OSMO_CC_ISDN_CAUSE_CALL_REJECTED;
|
||||
|
||||
/* third prio: return other than 88 and 18 (what ever was first) */
|
||||
if (old_cause != OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST
|
||||
&& old_cause != OSMO_CC_ISDN_CAUSE_USER_NOTRESPOND)
|
||||
return old_cause;
|
||||
if (new_cause != OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST
|
||||
&& new_cause != OSMO_CC_ISDN_CAUSE_USER_NOTRESPOND)
|
||||
return new_cause;
|
||||
|
||||
/* fourth prio: return 88 */
|
||||
if (old_cause == OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST
|
||||
|| new_cause == OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST)
|
||||
return OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST;
|
||||
|
||||
/* fith prio: return 18 */
|
||||
return OSMO_CC_ISDN_CAUSE_USER_NOTRESPOND;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
void osmo_cc_convert_cause(struct osmo_cc_ie_cause *cause);
|
||||
void osmo_cc_convert_cause_msg(osmo_cc_msg_t *msg);
|
||||
uint8_t osmo_cc_collect_cause(uint8_t old_cause, uint8_t new_cause);
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,131 +0,0 @@
|
|||
#ifndef OSMO_CC_ENDPOINT_H
|
||||
#define OSMO_CC_ENDPOINT_H
|
||||
|
||||
#include "message.h"
|
||||
#include "socket.h"
|
||||
#include "cause.h"
|
||||
|
||||
/* special osmo-cc error codes */
|
||||
#define OSMO_CC_RC_SEE_ERRNO -1
|
||||
#define OSMO_CC_RC_VERSION_MISMATCH 1
|
||||
|
||||
#define OSMO_CC_ATTACH_TIMER 2
|
||||
|
||||
/* call control state */
|
||||
enum osmo_cc_state {
|
||||
OSMO_CC_STATE_IDLE = 0,
|
||||
/* call states */
|
||||
OSMO_CC_STATE_INIT_OUT, /* outgoing CC-SETUP-REQ sent */
|
||||
OSMO_CC_STATE_INIT_IN, /* incoming CC-SETUP-IND received */
|
||||
OSMO_CC_STATE_OVERLAP_OUT, /* received CC-SETUP-ACK-IND on outgoing call */
|
||||
OSMO_CC_STATE_OVERLAP_IN, /* sent CC-SETUP-ACK-REQ on incoming call */
|
||||
OSMO_CC_STATE_PROCEEDING_OUT, /* received CC-PROC-IND on outgoing call */
|
||||
OSMO_CC_STATE_PROCEEDING_IN, /* sent CC-PROC-REQ on incoming call */
|
||||
OSMO_CC_STATE_ALERTING_OUT, /* received CC-ALERT-IND on outgoing call */
|
||||
OSMO_CC_STATE_ALERTING_IN, /* sent CC-ALERT-REQ on incoming call */
|
||||
OSMO_CC_STATE_CONNECTING_OUT, /* received CC-SETUP-CNF on outgoing call */
|
||||
OSMO_CC_STATE_CONNECTING_IN, /* sent CC-SETUP-RSP on incoming call */
|
||||
OSMO_CC_STATE_ACTIVE, /* received or sent CC-SETUP-COMPL-* */
|
||||
OSMO_CC_STATE_DISCONNECTING_OUT, /* sent CC-DISC-REQ */
|
||||
OSMO_CC_STATE_DISCONNECTING_IN, /* received CC-DISC-IND */
|
||||
OSMO_CC_STATE_DISC_COLLISION, /* received CC-DISC-IND after sending CC-DISC_REQ */
|
||||
OSMO_CC_STATE_RELEASING_OUT, /* sent CC-REL-REQ */
|
||||
/* attachment states */
|
||||
OSMO_CC_STATE_ATTACH_SENT, /* outgoing CC-ATT-REQ sent to socket */
|
||||
OSMO_CC_STATE_ATTACH_OUT, /* received CC-ATT-RSP on outgoing socket */
|
||||
OSMO_CC_STATE_ATTACH_WAIT, /* wait for outgoing attachment after failure */
|
||||
OSMO_CC_STATE_ATTACH_IN, /* incoming CC-ATT-REQ received from socket*/
|
||||
};
|
||||
|
||||
/* sample type */
|
||||
typedef int16_t osmo_cc_sample_t;
|
||||
|
||||
#define OSMO_CC_SAMPLE_MILLIWATT 23170 /* peak sine at -3 dB of full sample range */
|
||||
#define OSMO_CC_SAMPLE_SPEECH 3672 /* peak speech at -16 dB of milliwatt */
|
||||
#define OSMO_CC_SAMPLE_MIN -32768 /* lowest level */
|
||||
#define OSMO_CC_SAMPLE_MAX 32767 /* highest level */
|
||||
|
||||
#include "session.h"
|
||||
|
||||
struct osmo_cc_call;
|
||||
|
||||
typedef struct osmo_cc_screen_list {
|
||||
struct osmo_cc_screen_list *next;
|
||||
int has_from_type;
|
||||
uint8_t from_type;
|
||||
int has_from_present;
|
||||
uint8_t from_present;
|
||||
char from[128];
|
||||
int has_to_type;
|
||||
uint8_t to_type;
|
||||
int has_to_present;
|
||||
uint8_t to_present;
|
||||
char to[128];
|
||||
} osmo_cc_screen_list_t;
|
||||
|
||||
/* endpoint instance */
|
||||
typedef struct osmo_cc_endpoint {
|
||||
struct osmo_cc_endpoint *next;
|
||||
void *priv;
|
||||
void (*ll_msg_cb)(struct osmo_cc_endpoint *ep, uint32_t callref, osmo_cc_msg_t *msg);
|
||||
void (*ul_msg_cb)(struct osmo_cc_call *call, osmo_cc_msg_t *msg);
|
||||
osmo_cc_msg_list_t *ll_queue; /* messages towards lower layer */
|
||||
struct osmo_cc_call *call_list;
|
||||
const char *local_name; /* name of interface */
|
||||
const char *local_address; /* host+port */
|
||||
const char *local_host;
|
||||
uint16_t local_port;
|
||||
const char *remote_address; /* host+port */
|
||||
const char *remote_host;
|
||||
uint16_t remote_port;
|
||||
uint8_t serving_location;
|
||||
osmo_cc_socket_t os;
|
||||
osmo_cc_screen_list_t *screen_calling_in;
|
||||
osmo_cc_screen_list_t *screen_called_in;
|
||||
osmo_cc_screen_list_t *screen_calling_out;
|
||||
osmo_cc_screen_list_t *screen_called_out;
|
||||
int remote_auto; /* automatic remote address */
|
||||
struct timer attach_timer; /* timer to retry attachment */
|
||||
osmo_cc_session_config_t session_config; /* SDP/RTP default configuration */
|
||||
} osmo_cc_endpoint_t;
|
||||
|
||||
extern osmo_cc_endpoint_t *osmo_cc_endpoint_list;
|
||||
|
||||
/* call process */
|
||||
typedef struct osmo_cc_call {
|
||||
struct osmo_cc_call *next;
|
||||
osmo_cc_endpoint_t *ep;
|
||||
enum osmo_cc_state state;
|
||||
int lower_layer_released; /* when lower layer sent release, while upper layer gets a disconnect */
|
||||
int upper_layer_released; /* when upper layer sent release, while lower layer gets a disconnect */
|
||||
uint32_t callref;
|
||||
osmo_cc_msg_list_t *sock_queue; /* messages from socket */
|
||||
const char *attached_host; /* host and port from remote peer that attached to us */
|
||||
uint16_t attached_port;
|
||||
const char *attached_name; /* interface name from remote peer that attached to us */
|
||||
} osmo_cc_call_t;
|
||||
|
||||
/* returns 0 if ok
|
||||
* returns <0 for error as indicated
|
||||
* returns >=1 to indicate osmo-cc error code
|
||||
*/
|
||||
|
||||
void osmo_cc_help(void);
|
||||
int osmo_cc_new(osmo_cc_endpoint_t *ep, const char *version, const char *name, uint8_t serving_location, void (*ll_msg_cb)(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg), void (*ul_msg_cb)(osmo_cc_call_t *call, osmo_cc_msg_t *msg), void *priv, int argc, const char *argv[]);
|
||||
void osmo_cc_delete(struct osmo_cc_endpoint *ep);
|
||||
int osmo_cc_handle(void);
|
||||
osmo_cc_call_t *osmo_cc_call_by_callref(osmo_cc_endpoint_t *ep, uint32_t callref);
|
||||
osmo_cc_call_t *osmo_cc_get_attached_interface(osmo_cc_endpoint_t *ep, const char *interface);
|
||||
void osmo_cc_ll_msg(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg);
|
||||
void osmo_cc_ul_msg(void *priv, uint32_t callref, osmo_cc_msg_t *msg);
|
||||
osmo_cc_call_t *osmo_cc_call_new(osmo_cc_endpoint_t *ep);
|
||||
void osmo_cc_call_delete(struct osmo_cc_call *call);
|
||||
enum osmo_cc_session_addrtype osmo_cc_address_type(const char *address);
|
||||
const char *osmo_cc_host_of_address(const char *address);
|
||||
const char *osmo_cc_port_of_address(const char *address);
|
||||
|
||||
#include "rtp.h"
|
||||
#include "sdp.h"
|
||||
#include "screen.h"
|
||||
|
||||
#endif /* OSMO_CC_ENDPOINT_H */
|
|
@ -1,191 +0,0 @@
|
|||
/* Osmo-CC: helpers to simplify Osmo-CC usage
|
||||
*
|
||||
* (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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
#include <inttypes.h>
|
||||
#include "../libtimer/timer.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "endpoint.h"
|
||||
#include "helper.h"
|
||||
|
||||
osmo_cc_session_t *osmo_cc_helper_audio_offer(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, int debug)
|
||||
{
|
||||
osmo_cc_session_t *session;
|
||||
osmo_cc_session_media_t *media;
|
||||
const char *sdp;
|
||||
int i;
|
||||
|
||||
session = osmo_cc_new_session(conf, priv, NULL, NULL, NULL, 0, 0, NULL, NULL, debug);
|
||||
if (!session)
|
||||
return NULL;
|
||||
|
||||
media = osmo_cc_add_media(session, 0, 0, NULL, osmo_cc_session_media_type_audio, 0, osmo_cc_session_media_proto_rtp, 1, 1, receiver, debug);
|
||||
osmo_cc_rtp_open(media);
|
||||
|
||||
for (i = 0; codecs[i].payload_name; i++)
|
||||
osmo_cc_add_codec(media, codecs[i].payload_name, codecs[i].payload_rate, codecs[i].payload_channels, codecs[i].encoder, codecs[i].decoder, debug);
|
||||
|
||||
sdp = osmo_cc_session_send_offer(session);
|
||||
osmo_cc_add_ie_sdp(msg, sdp);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
const char *osmo_cc_helper_audio_accept(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p, int force_our_codec)
|
||||
{
|
||||
char offer_sdp[65536];
|
||||
const char *accept_sdp;
|
||||
osmo_cc_session_media_t *media, *selected_media;
|
||||
osmo_cc_session_codec_t *codec, *selected_codec, *telephone_event;
|
||||
int rc;
|
||||
int i, selected_codec_i, telephone_event_i;
|
||||
|
||||
if (*session_p) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Session already set, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
if (*codec_p) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Codec already set, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
/* SDP IE */
|
||||
rc = osmo_cc_get_ie_sdp(msg, 0, offer_sdp, sizeof(offer_sdp));
|
||||
if (rc < 0) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "There is no SDP included in setup request.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*session_p = osmo_cc_session_receive_offer(conf, priv, offer_sdp);
|
||||
if (!*session_p) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Failed to parse SDP.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
selected_media = NULL;
|
||||
osmo_cc_session_for_each_media((*session_p)->media_list, media) {
|
||||
/* only audio */
|
||||
if (media->description.type != osmo_cc_session_media_type_audio)
|
||||
continue;
|
||||
selected_codec_i = -1;
|
||||
selected_codec = NULL;
|
||||
telephone_event_i = -1;
|
||||
telephone_event = NULL;
|
||||
osmo_cc_session_for_each_codec(media->codec_list, codec) {
|
||||
if (!!strcasecmp(codec->payload_name, "telephone-event")) {
|
||||
for (i = 0; codecs[i].payload_name; i++) {
|
||||
if (osmo_cc_session_if_codec(codec, codecs[i].payload_name, codecs[i].payload_rate, codecs[i].payload_channels)) {
|
||||
/* select the first matchting codec or the one we prefer */
|
||||
if (selected_codec_i < 0 || i < selected_codec_i) {
|
||||
selected_codec = codec;
|
||||
selected_codec_i = i;
|
||||
selected_media = media;
|
||||
}
|
||||
/* if we don't force our preferred codec, use the preferred one from the remote */
|
||||
if (!force_our_codec)
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* special case: add telephone-event, if supported */
|
||||
for (i = 0; codecs[i].payload_name; i++) {
|
||||
if (!!strcasecmp(codecs[i].payload_name, "telephone-event"))
|
||||
continue;
|
||||
telephone_event = codec;
|
||||
telephone_event_i = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* codec is selected within this media, we are done */
|
||||
if (selected_codec)
|
||||
break;
|
||||
}
|
||||
if (!selected_codec) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "No codec found in setup message that we support.\n");
|
||||
osmo_cc_free_session(*session_p);
|
||||
return NULL;
|
||||
}
|
||||
osmo_cc_session_accept_codec(selected_codec, codecs[selected_codec_i].encoder, codecs[selected_codec_i].decoder);
|
||||
if (telephone_event)
|
||||
osmo_cc_session_accept_codec(telephone_event, codecs[telephone_event_i].encoder, codecs[telephone_event_i].decoder);
|
||||
osmo_cc_session_accept_media(selected_media, 0, 0, NULL, 1, 1, receiver);
|
||||
osmo_cc_rtp_open(selected_media);
|
||||
osmo_cc_rtp_connect(selected_media);
|
||||
*codec_p = selected_codec;
|
||||
|
||||
accept_sdp = osmo_cc_session_send_answer(*session_p);
|
||||
if (!accept_sdp) {
|
||||
osmo_cc_free_session(*session_p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return accept_sdp;
|
||||
}
|
||||
|
||||
int osmo_cc_helper_audio_negotiate(osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p)
|
||||
{
|
||||
char sdp[65536];
|
||||
osmo_cc_session_media_t *media;
|
||||
int rc;
|
||||
|
||||
if (!(*session_p)) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Session not set, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
/* once done, just ignore further messages that reply to setup */
|
||||
if (*codec_p)
|
||||
return 0;
|
||||
|
||||
/* SDP IE */
|
||||
rc = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp));
|
||||
if (rc < 0)
|
||||
return 0; // no reply in this message
|
||||
|
||||
rc = osmo_cc_session_receive_answer(*session_p, sdp);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
osmo_cc_session_for_each_media((*session_p)->media_list, media) {
|
||||
/* only audio */
|
||||
if (media->description.type != osmo_cc_session_media_type_audio)
|
||||
continue;
|
||||
/* select first codec, if one was accpeted */
|
||||
if (media->codec_list)
|
||||
*codec_p = media->codec_list;
|
||||
if (*codec_p) {
|
||||
osmo_cc_rtp_connect(media);
|
||||
/* no more media streams */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!(*codec_p)) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "No codec found in setup reply message that we support.\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
struct osmo_cc_helper_audio_codecs {
|
||||
const char *payload_name;
|
||||
uint32_t payload_rate;
|
||||
int payload_channels;
|
||||
void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
};
|
||||
|
||||
osmo_cc_session_t *osmo_cc_helper_audio_offer(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, int debug);
|
||||
const char *osmo_cc_helper_audio_accept(osmo_cc_session_config_t *conf, void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p, int force_our_codec);
|
||||
int osmo_cc_helper_audio_negotiate(osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p);
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,508 +0,0 @@
|
|||
#ifndef OSMO_CC_MSG_H
|
||||
#define OSMO_CC_MSG_H
|
||||
|
||||
#define OSMO_CC_VERSION "OSMOCCv1"
|
||||
|
||||
/* call control messages types */
|
||||
enum osmo_cc_msg_type {
|
||||
OSMO_CC_MSG_SETUP_REQ = 0x00,
|
||||
OSMO_CC_MSG_SETUP_IND = 0x01,
|
||||
OSMO_CC_MSG_REJ_REQ = 0x10,
|
||||
OSMO_CC_MSG_REJ_IND = 0x11,
|
||||
OSMO_CC_MSG_SETUP_ACK_REQ = 0x20,
|
||||
OSMO_CC_MSG_SETUP_ACK_IND = 0x21,
|
||||
OSMO_CC_MSG_PROC_REQ = 0x30,
|
||||
OSMO_CC_MSG_PROC_IND = 0x31,
|
||||
OSMO_CC_MSG_ALERT_REQ = 0x40,
|
||||
OSMO_CC_MSG_ALERT_IND = 0x41,
|
||||
OSMO_CC_MSG_SETUP_RSP = 0x02,
|
||||
OSMO_CC_MSG_SETUP_CNF = 0x03,
|
||||
OSMO_CC_MSG_SETUP_COMP_REQ = 0x50,
|
||||
OSMO_CC_MSG_SETUP_COMP_IND = 0x51,
|
||||
OSMO_CC_MSG_DISC_REQ = 0x60,
|
||||
OSMO_CC_MSG_DISC_IND = 0x61,
|
||||
OSMO_CC_MSG_REL_REQ = 0x70,
|
||||
OSMO_CC_MSG_REL_CNF = 0x73,
|
||||
OSMO_CC_MSG_REL_IND = 0x71,
|
||||
OSMO_CC_MSG_PROGRESS_REQ = 0x80,
|
||||
OSMO_CC_MSG_PROGRESS_IND = 0x81,
|
||||
OSMO_CC_MSG_NOTIFY_REQ = 0x84,
|
||||
OSMO_CC_MSG_NOTIFY_IND = 0x85,
|
||||
OSMO_CC_MSG_INFO_REQ = 0x88,
|
||||
OSMO_CC_MSG_INFO_IND = 0x89,
|
||||
OSMO_CC_MSG_ATTACH_REQ = 0xf8,
|
||||
OSMO_CC_MSG_ATTACH_IND = 0xf9,
|
||||
OSMO_CC_MSG_ATTACH_RSP = 0xfa,
|
||||
OSMO_CC_MSG_ATTACH_CNF = 0xfb,
|
||||
OSMO_CC_MSG_DUMMY_REQ = 0xfc,
|
||||
};
|
||||
#define OSMO_CC_MSG_NUM 0x100
|
||||
|
||||
#define OSMO_CC_MSG_MASK 0x03,
|
||||
#define OSMO_CC_MSG_REQ 0x00,
|
||||
#define OSMO_CC_MSG_IND 0x01,
|
||||
#define OSMO_CC_MSG_RSP 0x02,
|
||||
#define OSMO_CC_MSG_CNF 0x03,
|
||||
|
||||
const char *osmo_cc_msg_value2name(int value);
|
||||
int osmo_cc_msg_name2value(const char *name);
|
||||
|
||||
/* information elements */
|
||||
enum osmo_cc_ie_type {
|
||||
OSMO_CC_IE_CALLED = 0x11,
|
||||
OSMO_CC_IE_CALLED_SUB = 0x12,
|
||||
OSMO_CC_IE_CALLED_NAME = 0x13,
|
||||
OSMO_CC_IE_CALLED_INTERFACE = 0x14,
|
||||
OSMO_CC_IE_DTMF = 0x1d,
|
||||
OSMO_CC_IE_KEYPAD = 0x1e,
|
||||
OSMO_CC_IE_COMPLETE = 0x1f,
|
||||
OSMO_CC_IE_CALLING = 0x21,
|
||||
OSMO_CC_IE_CALLING_SUB = 0x22,
|
||||
OSMO_CC_IE_CALLING_NAME = 0x23,
|
||||
OSMO_CC_IE_CALLING_INTERFACE = 0x24,
|
||||
OSMO_CC_IE_CALLING_NETWORK = 0x2f,
|
||||
OSMO_CC_IE_REDIR = 0x31,
|
||||
OSMO_CC_IE_PROGRESS = 0x32,
|
||||
OSMO_CC_IE_NOTIFY = 0x33,
|
||||
OSMO_CC_IE_DISPLAY = 0x34,
|
||||
OSMO_CC_IE_CAUSE = 0x41,
|
||||
OSMO_CC_IE_BEARER = 0x51,
|
||||
OSMO_CC_IE_SDP = 0x52,
|
||||
OSMO_CC_IE_SOCKET_ADDRESS = 0x5e,
|
||||
OSMO_CC_IE_PRIVATE = 0x5f,
|
||||
};
|
||||
#define OSMO_CC_IE_NUM 0x100
|
||||
|
||||
const char *osmo_cc_ie_value2name(int value);
|
||||
int osmo_cc_ie_name2value(const char *name);
|
||||
|
||||
/* type of number, see ITU-T Rec. Q.931 */
|
||||
#define OSMO_CC_TYPE_UNKNOWN 0
|
||||
#define OSMO_CC_TYPE_INTERNATIONAL 1
|
||||
#define OSMO_CC_TYPE_NATIONAL 2
|
||||
#define OSMO_CC_TYPE_NETWORK 3
|
||||
#define OSMO_CC_TYPE_SUBSCRIBER 4
|
||||
#define OSMO_CC_TYPE_ABBREVIATED 5
|
||||
#define OSMO_CC_TYPE_RESERVED 7
|
||||
#define OSMO_CC_TYPE_NUM 8
|
||||
|
||||
const char *osmo_cc_type_value2name(int value);
|
||||
int osmo_cc_type_name2value(const char *name);
|
||||
|
||||
/* numbering plan, see ITU-T Rec. Q.931 */
|
||||
#define OSMO_CC_PLAN_UNKNOWN 0
|
||||
#define OSMO_CC_PLAN_TELEPHONY 1
|
||||
#define OSMO_CC_PLAN_DATA 3
|
||||
#define OSMO_CC_PLAN_TTY 4
|
||||
#define OSMO_CC_PLAN_NATIONAL_STANDARD 8
|
||||
#define OSMO_CC_PLAN_PRIVATE 9
|
||||
#define OSMO_CC_PLAN_RESERVED 15
|
||||
#define OSMO_CC_PLAN_NUM 16
|
||||
|
||||
const char *osmo_cc_plan_value2name(int value);
|
||||
int osmo_cc_plan_name2value(const char *name);
|
||||
|
||||
/* presentation indicator, see ITU-T Rec. Q.931 */
|
||||
#define OSMO_CC_PRESENT_ALLOWED 0
|
||||
#define OSMO_CC_PRESENT_RESTRICTED 1
|
||||
#define OSMO_CC_PRESENT_NOT_AVAIL 2
|
||||
#define OSMO_CC_PRESENT_RESERVED 3
|
||||
#define OSMO_CC_PRESENT_NUM 4
|
||||
|
||||
const char *osmo_cc_present_value2name(int value);
|
||||
int osmo_cc_present_name2value(const char *name);
|
||||
|
||||
/* screening indicator, see ITU-T Rec. Q.931 */
|
||||
#define OSMO_CC_SCREEN_USER_UNSCREENED 0
|
||||
#define OSMO_CC_SCREEN_USER_VERIFIED_PASSED 1
|
||||
#define OSMO_CC_SCREEN_USER_VERIFIED_FAILED 2
|
||||
#define OSMO_CC_SCREEN_NETWORK 3
|
||||
#define OSMO_CC_SCREEN_NUM 4
|
||||
|
||||
const char *osmo_cc_screen_value2name(int value);
|
||||
int osmo_cc_screen_name2value(const char *name);
|
||||
|
||||
/* screening indicator, see ITU-T Rec. Q.931 */
|
||||
#define OSMO_CC_REDIR_REASON_UNKNOWN 0
|
||||
#define OSMO_CC_REDIR_REASON_CFB 1
|
||||
#define OSMO_CC_REDIR_REASON_CFNR 2
|
||||
#define OSMO_CC_REDIR_REASON_CD 4
|
||||
#define OSMO_CC_REDIR_REASON_CF_OUTOFORDER 9
|
||||
#define OSMO_CC_REDIR_REASON_CF_BY_DTE 10
|
||||
#define OSMO_CC_REDIR_REASON_CFU 15
|
||||
#define OSMO_CC_REDIR_REASON_NUM 16
|
||||
|
||||
const char *osmo_cc_redir_reason_value2name(int value);
|
||||
int osmo_cc_redir_reason_name2value(const char *name);
|
||||
|
||||
/* notification indicator, see ITU-T Rec. Q.931 ff. */
|
||||
#define OSMO_CC_NOTIFY_USER_SUSPENDED 0x00
|
||||
#define OSMO_CC_NOTIFY_USER_RESUMED 0x01
|
||||
#define OSMO_CC_NOTIFY_BEARER_SERVICE_CHANGE 0x02
|
||||
#define OSMO_CC_NOTIFY_CALL_COMPLETION_DELAY 0x03
|
||||
#define OSMO_CC_NOTIFY_CONFERENCE_ESTABLISHED 0x42
|
||||
#define OSMO_CC_NOTIFY_CONFERENCE_DISCONNECTED 0x43
|
||||
#define OSMO_CC_NOTIFY_OTHER_PARTY_ADDED 0x44
|
||||
#define OSMO_CC_NOTIFY_ISOLATED 0x45
|
||||
#define OSMO_CC_NOTIFY_REATTACHED 0x46
|
||||
#define OSMO_CC_NOTIFY_OTHER_PARTY_ISOLATED 0x47
|
||||
#define OSMO_CC_NOTIFY_OTHER_PARTY_REATTACHED 0x48
|
||||
#define OSMO_CC_NOTIFY_OTHER_PARTY_SPLIT 0x49
|
||||
#define OSMO_CC_NOTIFY_OTHER_PARTY_DISCONNECTED 0x4a
|
||||
#define OSMO_CC_NOTIFY_CONFERENCE_FLOATING 0x4b
|
||||
#define OSMO_CC_NOTIFY_CONFERENCE_DISC_PREEMPT 0x4c /* disconnect preemted */
|
||||
#define OSMO_CC_NOTIFY_CONFERENCE_FLOATING_SUP 0x4f /* served user preemted */
|
||||
#define OSMO_CC_NOTIFY_CALL_IS_A_WAITING_CALL 0x60
|
||||
#define OSMO_CC_NOTIFY_DIVERSION_ACTIVATED 0x68
|
||||
#define OSMO_CC_NOTIFY_RESERVED_CT_1 0x69
|
||||
#define OSMO_CC_NOTIFY_RESERVED_CT_2 0x6a
|
||||
#define OSMO_CC_NOTIFY_REVERSE_CHARGING 0x6e
|
||||
#define OSMO_CC_NOTIFY_REMOTE_HOLD 0x79
|
||||
#define OSMO_CC_NOTIFY_REMOTE_RETRIEVAL 0x7a
|
||||
#define OSMO_CC_NOTIFY_CALL_IS_DIVERTING 0x7b
|
||||
#define OSMO_CC_NOTIFY_NUM 0x100
|
||||
|
||||
const char *osmo_cc_notify_value2name(int value);
|
||||
int osmo_cc_notify_name2value(const char *name);
|
||||
|
||||
/* coding standard, see ITU-T Rec. Q.931 */
|
||||
#define OSMO_CC_CODING_ITU_T 0
|
||||
#define OSMO_CC_CODING_ISO_IEC 1
|
||||
#define OSMO_CC_CODING_NATIONAL 2
|
||||
#define OSMO_CC_CODING_STANDARD_SPECIFIC 3
|
||||
#define OSMO_CC_CODING_NUM 4
|
||||
|
||||
const char *osmo_cc_coding_value2name(int value);
|
||||
int osmo_cc_coding_name2value(const char *name);
|
||||
|
||||
/* cause, see ITU-T Rec. Q.850 */
|
||||
#define OSMO_CC_ISDN_CAUSE_UNASSIGNED_NR 1
|
||||
#define OSMO_CC_ISDN_CAUSE_NO_ROUTE_TRANSIT 2
|
||||
#define OSMO_CC_ISDN_CAUSE_NO_ROUTE 3
|
||||
#define OSMO_CC_ISDN_CAUSE_CHAN_UNACCEPT 6
|
||||
#define OSMO_CC_ISDN_CAUSE_OP_DET_BARRING 8
|
||||
#define OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR 16
|
||||
#define OSMO_CC_ISDN_CAUSE_USER_BUSY 17
|
||||
#define OSMO_CC_ISDN_CAUSE_USER_NOTRESPOND 18
|
||||
#define OSMO_CC_ISDN_CAUSE_USER_ALERTING_NA 19
|
||||
#define OSMO_CC_ISDN_CAUSE_CALL_REJECTED 21
|
||||
#define OSMO_CC_ISDN_CAUSE_NUMBER_CHANGED 22
|
||||
#define OSMO_CC_ISDN_CAUSE_PRE_EMPTION 25
|
||||
#define OSMO_CC_ISDN_CAUSE_NONSE_USER_CLR 26
|
||||
#define OSMO_CC_ISDN_CAUSE_DEST_OOO 27
|
||||
#define OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT 28
|
||||
#define OSMO_CC_ISDN_CAUSE_FACILITY_REJ 29
|
||||
#define OSMO_CC_ISDN_CAUSE_RESP_STATUS_INQ 30
|
||||
#define OSMO_CC_ISDN_CAUSE_NORMAL_UNSPEC 31
|
||||
#define OSMO_CC_ISDN_CAUSE_NO_CIRCUIT_CHAN 34
|
||||
#define OSMO_CC_ISDN_CAUSE_NETWORK_OOO 38
|
||||
#define OSMO_CC_ISDN_CAUSE_TEMP_FAILURE 41
|
||||
#define OSMO_CC_ISDN_CAUSE_SWITCH_CONG 42
|
||||
#define OSMO_CC_ISDN_CAUSE_ACC_INF_DISCARD 43
|
||||
#define OSMO_CC_ISDN_CAUSE_REQ_CHAN_UNAVAIL 44
|
||||
#define OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL 47
|
||||
#define OSMO_CC_ISDN_CAUSE_QOS_UNAVAIL 49
|
||||
#define OSMO_CC_ISDN_CAUSE_REQ_FAC_NOT_SUBSC 50
|
||||
#define OSMO_CC_ISDN_CAUSE_INC_BARRED_CUG 55
|
||||
#define OSMO_CC_ISDN_CAUSE_BEARER_CAP_UNAUTH 57
|
||||
#define OSMO_CC_ISDN_CAUSE_BEARER_CA_UNAVAIL 58
|
||||
#define OSMO_CC_ISDN_CAUSE_SERV_OPT_UNAVAIL 63
|
||||
#define OSMO_CC_ISDN_CAUSE_BEARERSERV_UNIMPL 65
|
||||
#define OSMO_CC_ISDN_CAUSE_ACM_GE_ACM_MAX 68
|
||||
#define OSMO_CC_ISDN_CAUSE_REQ_FAC_NOTIMPL 69
|
||||
#define OSMO_CC_ISDN_CAUSE_RESTR_BCAP_AVAIL 70
|
||||
#define OSMO_CC_ISDN_CAUSE_SERV_OPT_UNIMPL 79
|
||||
#define OSMO_CC_ISDN_CAUSE_INVAL_CALLREF 81
|
||||
#define OSMO_CC_ISDN_CAUSE_USER_NOT_IN_CUG 87
|
||||
#define OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST 88
|
||||
#define OSMO_CC_ISDN_CAUSE_INVAL_TRANS_NET 91
|
||||
#define OSMO_CC_ISDN_CAUSE_SEMANTIC_INCORR 95
|
||||
#define OSMO_CC_ISDN_CAUSE_INVAL_MAND_INF 96
|
||||
#define OSMO_CC_ISDN_CAUSE_MSGTYPE_NOTEXIST 97
|
||||
#define OSMO_CC_ISDN_CAUSE_MSGTYPE_INCOMPAT 98
|
||||
#define OSMO_CC_ISDN_CAUSE_IE_NOTEXIST 99
|
||||
#define OSMO_CC_ISDN_CAUSE_COND_IE_ERR 100
|
||||
#define OSMO_CC_ISDN_CAUSE_MSG_INCOMP_STATE 101
|
||||
#define OSMO_CC_ISDN_CAUSE_RECOVERY_TIMER 102
|
||||
#define OSMO_CC_ISDN_CAUSE_PROTO_ERR 111
|
||||
#define OSMO_CC_ISDN_CAUSE_INTERWORKING 127
|
||||
#define OSMO_CC_ISDN_CAUSE_NUM 128
|
||||
|
||||
const char *osmo_cc_isdn_cause_value2name(int value);
|
||||
int osmo_cc_isdn_cause_name2value(const char *name);
|
||||
|
||||
/* location, see ITU-T Rec. Q.931 */
|
||||
#define OSMO_CC_LOCATION_USER 0
|
||||
#define OSMO_CC_LOCATION_PRIV_SERV_LOC_USER 1
|
||||
#define OSMO_CC_LOCATION_PUB_SERV_LOC_USER 2
|
||||
#define OSMO_CC_LOCATION_TRANSIT 3
|
||||
#define OSMO_CC_LOCATION_PUB_SERV_REM_USER 4
|
||||
#define OSMO_CC_LOCATION_PRIV_SERV_REM_USER 5
|
||||
#define OSMO_CC_LOCATION_BEYOND_INTERWORKING 10
|
||||
#define OSMO_CC_LOCATION_NUM 16
|
||||
|
||||
const char *osmo_cc_location_value2name(int value);
|
||||
int osmo_cc_location_name2value(const char *name);
|
||||
|
||||
/* progress description, see ITU-T Rec. Q.931 */
|
||||
#define OSMO_CC_PROGRESS_NOT_END_TO_END_ISDN 1
|
||||
#define OSMO_CC_PROGRESS_DEST_NOT_ISDN 2
|
||||
#define OSMO_CC_PROGRESS_ORIG_NOT_ISDN 3
|
||||
#define OSMO_CC_PROGRESS_RETURN_TO_ISDN 4
|
||||
#define OSMO_CC_PROGRESS_INTERWORKING 5
|
||||
#define OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE 8
|
||||
#define OSMO_CC_PROGRESS_NUM 16
|
||||
|
||||
const char *osmo_cc_progress_value2name(int value);
|
||||
int osmo_cc_progress_name2value(const char *name);
|
||||
|
||||
/* information transfer capability, see ITU-T Rec. Q.931 */
|
||||
#define OSMO_CC_CAPABILITY_SPEECH 0
|
||||
#define OSMO_CC_CAPABILITY_DATA 8
|
||||
#define OSMO_CC_CAPABILITY_DATA_RESTRICTED 9
|
||||
#define OSMO_CC_CAPABILITY_AUDIO 16
|
||||
#define OSMO_CC_CAPABILITY_DATA_WITH_TONES 17
|
||||
#define OSMO_CC_CAPABILITY_VIDEO 24
|
||||
#define OSMO_CC_CAPABILITY_NUM 32
|
||||
|
||||
const char *osmo_cc_capability_value2name(int value);
|
||||
int osmo_cc_capability_name2value(const char *name);
|
||||
|
||||
/* transfer mode, see ITU-T Rec. Q.931 */
|
||||
#define OSMO_CC_MODE_CIRCUIT 0
|
||||
#define OSMO_CC_MODE_PACKET 2
|
||||
#define OSMO_CC_MODE_NUM 4
|
||||
|
||||
const char *osmo_cc_mode_value2name(int value);
|
||||
int osmo_cc_mode_name2value(const char *name);
|
||||
|
||||
#define OSMO_CC_DTMF_MODE_OFF 0 /* stop tone */
|
||||
#define OSMO_CC_DTMF_MODE_ON 1 /* start tone */
|
||||
#define OSMO_CC_DTMF_MODE_DIGITS 2 /* play tone(s) with duration and pauses */
|
||||
#define OSMO_CC_DTMF_MODE_NUM 3
|
||||
|
||||
const char *osmo_cc_dtmf_mode_value2name(int value);
|
||||
int osmo_cc_dtmf_mode_name2value(const char *name);
|
||||
|
||||
#define OSMO_CC_SOCKET_CAUSE_VERSION_MISMATCH 1 /* version mismatch */
|
||||
#define OSMO_CC_SOCKET_CAUSE_FAILED 2 /* connection failed */
|
||||
#define OSMO_CC_SOCKET_CAUSE_BROKEN_PIPE 3 /* connected socket failed */
|
||||
#define OSMO_CC_SOCKET_CAUSE_TIMEOUT 4 /* keepalive packets timeout */
|
||||
// if you add causes here, add them in process_cause.c also!
|
||||
#define OSMO_CC_SOCKET_CAUSE_NUM 5
|
||||
|
||||
const char *osmo_cc_socket_cause_value2name(int value);
|
||||
int osmo_cc_socket_cause_name2value(const char *name);
|
||||
|
||||
/* network type (network IE) and meaning of 'id' */
|
||||
#define OSMO_CC_NETWORK_UNDEFINED 0x00
|
||||
#define OSMO_CC_NETWORK_ALSA_NONE 0x01
|
||||
#define OSMO_CC_NETWORK_POTS_NONE 0x02
|
||||
#define OSMO_CC_NETWORK_ISDN_NONE 0x03
|
||||
#define OSMO_CC_NETWORK_SIP_NONE 0x04
|
||||
#define OSMO_CC_NETWORK_GSM_IMSI 0x05 /* id has decimal IMSI */
|
||||
#define OSMO_CC_NETWORK_GSM_IMEI 0x06 /* id has decimal IMEI */
|
||||
#define OSMO_CC_NETWORK_WEB_NONE 0x07
|
||||
#define OSMO_CC_NETWORK_DECT_NONE 0x08
|
||||
#define OSMO_CC_NETWORK_BLUETOOTH_NONE 0x09
|
||||
#define OSMO_CC_NETWORK_SS5_NONE 0x0a
|
||||
#define OSMO_CC_NETWORK_ANETZ_NONE 0x80
|
||||
#define OSMO_CC_NETWORK_BNETZ_MUENZ 0x81 /* id starts with 'M' */
|
||||
#define OSMO_CC_NETWORK_CNETZ_NONE 0x82
|
||||
#define OSMO_CC_NETWORK_NMT_NONE 0x83 /* id has decimal password */
|
||||
#define OSMO_CC_NETWORK_R2000_NONE 0x84
|
||||
#define OSMO_CC_NETWORK_AMPS_ESN 0x85 /* if has decimal ESN (TACS also) */
|
||||
#define OSMO_CC_NETWORK_MTS_NONE 0x86
|
||||
#define OSMO_CC_NETWORK_IMTS_NONE 0x87
|
||||
#define OSMO_CC_NETWORK_EUROSIGNAL_NONE 0x88
|
||||
#define OSMO_CC_NETWORK_JOLLYCOM_NONE 0x89 /* call from JollyCom... */
|
||||
#define OSMO_CC_NETWORK_MPT1327_PSTN 0x8a /* call from MPT1327 */
|
||||
#define OSMO_CC_NETWORK_MPT1327_PBX 0x8b /* id is selected PBX number */
|
||||
#define OSMO_CC_NETWORK_NUM 0x100
|
||||
|
||||
const char *osmo_cc_network_value2name(int value);
|
||||
int osmo_cc_network_name2value(const char *name);
|
||||
|
||||
typedef struct osmo_cc_msg {
|
||||
uint8_t type;
|
||||
uint16_t length_networkorder;
|
||||
uint8_t data[0];
|
||||
} __attribute__((packed)) osmo_cc_msg_t;
|
||||
|
||||
typedef struct osmo_cc_msg_list {
|
||||
struct osmo_cc_msg_list *next;
|
||||
struct osmo_cc_msg *msg;
|
||||
uint32_t callref;
|
||||
char host[128];
|
||||
uint16_t port;
|
||||
} osmo_cc_msg_list_t;
|
||||
|
||||
typedef struct osmo_cc_ie {
|
||||
uint8_t type;
|
||||
uint16_t length_networkorder;
|
||||
uint8_t data[0];
|
||||
} __attribute__((packed)) osmo_cc_ie_t;
|
||||
|
||||
struct osmo_cc_ie_called {
|
||||
uint8_t type;
|
||||
uint8_t plan;
|
||||
char digits[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_called_sub {
|
||||
uint8_t type;
|
||||
char digits[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_called_name {
|
||||
char name[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_called_interface {
|
||||
char name[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_calling {
|
||||
uint8_t type;
|
||||
uint8_t plan;
|
||||
uint8_t present;
|
||||
uint8_t screen;
|
||||
char digits[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_calling_sub {
|
||||
uint8_t type;
|
||||
char digits[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_calling_name {
|
||||
char name[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_calling_interface {
|
||||
char name[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_network {
|
||||
uint8_t type;
|
||||
char id[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_bearer {
|
||||
uint8_t coding;
|
||||
uint8_t capability;
|
||||
uint8_t mode;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_redir {
|
||||
uint8_t type;
|
||||
uint8_t plan;
|
||||
uint8_t present;
|
||||
uint8_t screen;
|
||||
uint8_t redir_reason;
|
||||
char digits[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_dtmf {
|
||||
uint8_t duration_ms;
|
||||
uint8_t pause_ms;
|
||||
uint8_t dtmf_mode;
|
||||
char digits[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_keypad {
|
||||
char digits[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_progress {
|
||||
uint8_t coding;
|
||||
uint8_t location;
|
||||
uint8_t progress;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_notify {
|
||||
uint8_t notify;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_cause {
|
||||
uint8_t location;
|
||||
uint8_t isdn_cause;
|
||||
uint16_t sip_cause_networkorder;
|
||||
uint8_t socket_cause;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_display {
|
||||
char text[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_sdp {
|
||||
char sdp[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_socket_address {
|
||||
char address[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct osmo_cc_ie_private {
|
||||
uint32_t unique_networkorder;
|
||||
uint8_t data[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
uint32_t osmo_cc_new_callref(void);
|
||||
osmo_cc_msg_t *osmo_cc_new_msg(uint8_t msg_type);
|
||||
osmo_cc_msg_t *osmo_cc_clone_msg(osmo_cc_msg_t *msg);
|
||||
osmo_cc_msg_t *osmo_cc_msg_list_dequeue(osmo_cc_msg_list_t **mlp, uint32_t *callref_p);
|
||||
osmo_cc_msg_list_t *osmo_cc_msg_list_enqueue(osmo_cc_msg_list_t **mlp, osmo_cc_msg_t *msg, uint32_t callref);
|
||||
void osmo_cc_free_msg(osmo_cc_msg_t *msg);
|
||||
void osmo_cc_debug_ie(osmo_cc_msg_t *msg, int level);
|
||||
int osmo_cc_get_ie_struct(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat, int ie_len, const osmo_cc_ie_t **ie_struct);
|
||||
int osmo_cc_get_ie_data(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat, int ie_len, const void **ie_data);
|
||||
int osmo_cc_has_ie(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat);
|
||||
int osmo_cc_remove_ie(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat);
|
||||
void *osmo_cc_add_ie(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_len);
|
||||
void *osmo_cc_msg_sep_ie(osmo_cc_msg_t *msg, void **iep, uint8_t *ie_type, uint16_t *ie_length);
|
||||
|
||||
void osmo_cc_add_ie_called(osmo_cc_msg_t *msg, uint8_t type, uint8_t plan, const char *dialing);
|
||||
int osmo_cc_get_ie_called(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, uint8_t *plan, char *dialing, size_t dialing_size);
|
||||
void osmo_cc_add_ie_called_sub(osmo_cc_msg_t *msg, uint8_t type, const char *dialing);
|
||||
int osmo_cc_get_ie_called_sub(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, char *dialing, size_t dialing_size);
|
||||
void osmo_cc_add_ie_called_name(osmo_cc_msg_t *msg, const char *name);
|
||||
int osmo_cc_get_ie_called_name(osmo_cc_msg_t *msg, int ie_repeat, char *name, size_t name_size);
|
||||
void osmo_cc_add_ie_called_interface(osmo_cc_msg_t *msg, const char *interface);
|
||||
int osmo_cc_get_ie_called_interface(osmo_cc_msg_t *msg, int ie_repeat, char *interface, size_t interface_size);
|
||||
void osmo_cc_add_ie_complete(osmo_cc_msg_t *msg);
|
||||
int osmo_cc_get_ie_complete(osmo_cc_msg_t *msg, int ie_repeat);
|
||||
void osmo_cc_add_ie_calling(osmo_cc_msg_t *msg, uint8_t type, uint8_t plan, uint8_t present, uint8_t screen, const char *callerid);
|
||||
int osmo_cc_get_ie_calling(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, uint8_t *plan, uint8_t *present, uint8_t *screen, char *callerid, size_t callerid_size);
|
||||
void osmo_cc_add_ie_calling_sub(osmo_cc_msg_t *msg, uint8_t type, const char *callerid);
|
||||
int osmo_cc_get_ie_calling_sub(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, char *callerid, size_t callerid_size);
|
||||
void osmo_cc_add_ie_calling_name(osmo_cc_msg_t *msg, const char *name);
|
||||
int osmo_cc_get_ie_calling_name(osmo_cc_msg_t *msg, int ie_repeat, char *name, size_t name_size);
|
||||
void osmo_cc_add_ie_calling_interface(osmo_cc_msg_t *msg, const char *interface);
|
||||
int osmo_cc_get_ie_calling_interface(osmo_cc_msg_t *msg, int ie_repeat, char *interface, size_t interface_size);
|
||||
void osmo_cc_add_ie_calling_network(osmo_cc_msg_t *msg, uint8_t type, const char *networkid);
|
||||
int osmo_cc_get_ie_calling_network(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, char *networkid, size_t networkid_size);
|
||||
void osmo_cc_add_ie_bearer(osmo_cc_msg_t *msg, uint8_t coding, uint8_t capability, uint8_t mode);
|
||||
int osmo_cc_get_ie_bearer(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *coding, uint8_t *capability, uint8_t *mode);
|
||||
void osmo_cc_add_ie_redir(osmo_cc_msg_t *msg, uint8_t type, uint8_t plan, uint8_t present, uint8_t screen, uint8_t redir_reason, const char *callerid);
|
||||
int osmo_cc_get_ie_redir(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, uint8_t *plan, uint8_t *present, uint8_t *screen, uint8_t *reason, char *callerid, size_t callerid_size);
|
||||
void osmo_cc_add_ie_dtmf(osmo_cc_msg_t *msg, uint8_t duration_ms, uint8_t pause_ms, uint8_t dtmf_mode, const char *digits);
|
||||
int osmo_cc_get_ie_dtmf(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *duration_ms, uint8_t *pause_ms, uint8_t *dtmf_mode, char *digits, size_t digits_size);
|
||||
void osmo_cc_add_ie_keypad(osmo_cc_msg_t *msg, const char *digits);
|
||||
int osmo_cc_get_ie_keypad(osmo_cc_msg_t *msg, int ie_repeat, char *digits, size_t digits_size);
|
||||
void osmo_cc_add_ie_progress(osmo_cc_msg_t *msg, uint8_t coding, uint8_t location, uint8_t progress);
|
||||
int osmo_cc_get_ie_progress(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *coding, uint8_t *location, uint8_t *progress);
|
||||
void osmo_cc_add_ie_notify(osmo_cc_msg_t *msg, uint8_t notify);
|
||||
int osmo_cc_get_ie_notify(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *notify);
|
||||
void osmo_cc_add_ie_cause(osmo_cc_msg_t *msg, uint8_t location, uint8_t isdn_cause, uint16_t sip_cause, uint8_t socket_cause);
|
||||
int osmo_cc_get_ie_cause(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *location, uint8_t *isdn_cause, uint16_t *sip_cause, uint8_t *socket_cause);
|
||||
void osmo_cc_add_ie_display(osmo_cc_msg_t *msg, const char *text);
|
||||
int osmo_cc_get_ie_display(osmo_cc_msg_t *msg, int ie_repeat, char *text, size_t text_size);
|
||||
void osmo_cc_add_ie_sdp(osmo_cc_msg_t *msg, const char *sdp);
|
||||
int osmo_cc_get_ie_sdp(osmo_cc_msg_t *msg, int ie_repeat, char *sdp, size_t sdp_size);
|
||||
void osmo_cc_add_ie_socket_address(osmo_cc_msg_t *msg, const char *address);
|
||||
int osmo_cc_get_ie_socket_address(osmo_cc_msg_t *msg, int ie_repeat, char *address, size_t address_size);
|
||||
void osmo_cc_add_ie_private(osmo_cc_msg_t *msg, uint32_t unique, const uint8_t *data, size_t data_size);
|
||||
int osmo_cc_get_ie_private(osmo_cc_msg_t *msg, int ie_repeat, uint32_t *unique, uint8_t *data, size_t data_size);
|
||||
|
||||
#endif /* OSMO_CC_MSG_H */
|
|
@ -1,403 +0,0 @@
|
|||
/* Osmo-CC: RTP 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 <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libtimer/timer.h"
|
||||
#include "endpoint.h"
|
||||
|
||||
#define RTP_VERSION 2
|
||||
|
||||
void osmo_cc_set_rtp_ports(osmo_cc_session_config_t *conf, uint16_t from, uint16_t to)
|
||||
{
|
||||
conf->rtp_port_next = from;
|
||||
conf->rtp_port_from = from;
|
||||
conf->rtp_port_to = to;
|
||||
}
|
||||
|
||||
struct rtp_hdr {
|
||||
uint8_t byte0;
|
||||
uint8_t byte1;
|
||||
uint16_t sequence;
|
||||
uint32_t timestamp;
|
||||
uint32_t ssrc;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct rtp_x_hdr {
|
||||
uint16_t by_profile;
|
||||
uint16_t length;
|
||||
} __attribute__((packed));
|
||||
|
||||
static int rtp_receive(int sock, uint8_t **payload_p, int *payload_len_p, uint8_t *marker_p, uint8_t *pt_p, uint16_t *sequence_p, uint32_t *timestamp_p)
|
||||
{
|
||||
static uint8_t data[2048];
|
||||
int len;
|
||||
struct rtp_hdr *rtph = (struct rtp_hdr *)data;
|
||||
uint8_t version, padding, extension, csrc_count, marker, payload_type;
|
||||
struct rtp_x_hdr *rtpxh;
|
||||
uint8_t *payload;
|
||||
int payload_len;
|
||||
int x_len;
|
||||
|
||||
len = read(sock, data, sizeof(data));
|
||||
if (len < 0) {
|
||||
if (errno == EAGAIN)
|
||||
return -EAGAIN;
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Read errno = %d (%s)\n", errno, strerror(errno));
|
||||
return -EIO;
|
||||
}
|
||||
if (len < 12) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short (len = %d).\n", len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
version = rtph->byte0 >> 6;
|
||||
padding = (rtph->byte0 >> 5) & 1;
|
||||
extension = (rtph->byte0 >> 4) & 1;
|
||||
csrc_count = rtph->byte0 & 0x0f;
|
||||
marker = rtph->byte1 >> 7;
|
||||
payload_type = rtph->byte1 & 0x7f;
|
||||
*sequence_p = ntohs(rtph->sequence);
|
||||
*timestamp_p = ntohl(rtph->timestamp);
|
||||
|
||||
if (version != RTP_VERSION) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Received RTP version %d not supported.\n", version);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
payload = data + sizeof(*rtph) + (csrc_count << 2);
|
||||
payload_len = len - sizeof(*rtph) - (csrc_count << 2);
|
||||
if (payload_len < 0) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short (len = %d, csrc count = %d).\n", len, csrc_count);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (extension) {
|
||||
if (payload_len < (int)sizeof(*rtpxh)) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short for extension header.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
rtpxh = (struct rtp_x_hdr *)payload;
|
||||
x_len = ntohs(rtpxh->length) * 4 + sizeof(*rtpxh);
|
||||
payload += x_len;
|
||||
payload_len -= x_len;
|
||||
if (payload_len < (int)sizeof(*rtpxh)) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short, extension header exceeds frame length.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (padding) {
|
||||
if (payload_len < 1) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short for padding length.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
payload_len -= payload[payload_len - 1];
|
||||
if (payload_len < 0) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame padding is greater than payload.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
*payload_p = payload;
|
||||
*payload_len_p = payload_len;
|
||||
*marker_p = marker;
|
||||
*pt_p = payload_type;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rtp_send(int sock, uint8_t *payload, int payload_len, uint8_t pt, uint16_t sequence, uint32_t timestamp, uint32_t ssrc)
|
||||
{
|
||||
struct rtp_hdr *rtph;
|
||||
char data[sizeof(*rtph) + payload_len];
|
||||
int len, rc;
|
||||
|
||||
rtph = (struct rtp_hdr *)data;
|
||||
len = sizeof(*rtph);
|
||||
rtph->byte0 = RTP_VERSION << 6;
|
||||
rtph->byte1 = pt;
|
||||
rtph->sequence = htons(sequence);
|
||||
rtph->timestamp = htonl(timestamp);
|
||||
rtph->ssrc = htonl(ssrc);
|
||||
len += payload_len;
|
||||
if (len > (int)sizeof(data)) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Buffer overflow, please fix!.\n");
|
||||
abort();
|
||||
}
|
||||
memcpy(data + sizeof(*rtph), payload, payload_len);
|
||||
|
||||
rc = write(sock, data, len);
|
||||
if (rc < 0)
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Write errno = %d (%s)\n", errno, strerror(errno));
|
||||
}
|
||||
|
||||
/* open and bind RTP
|
||||
* set local port to what we bound
|
||||
*/
|
||||
int osmo_cc_rtp_open(osmo_cc_session_media_t *media)
|
||||
{
|
||||
osmo_cc_session_config_t *conf = media->session->config;
|
||||
int domain = 0; // make GCC happy
|
||||
uint16_t start_port;
|
||||
struct sockaddr_storage sa;
|
||||
int slen = 0; // make GCC happy
|
||||
struct sockaddr_in6 *sa6;
|
||||
struct sockaddr_in *sa4;
|
||||
uint16_t *sport;
|
||||
int flags;
|
||||
int rc;
|
||||
|
||||
media->rtp_ssrc = rand();
|
||||
|
||||
osmo_cc_rtp_close(media);
|
||||
|
||||
switch (media->connection_data_local.addrtype) {
|
||||
case osmo_cc_session_addrtype_ipv4:
|
||||
domain = AF_INET;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa4 = (struct sockaddr_in *)&sa;
|
||||
sa4->sin_family = domain;
|
||||
rc = inet_pton(AF_INET, media->connection_data_local.address, &sa4->sin_addr);
|
||||
if (rc < 1) {
|
||||
pton_error:
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Cannot bind to address '%s'.\n", media->connection_data_local.address);
|
||||
return -EINVAL;
|
||||
}
|
||||
sport = &sa4->sin_port;
|
||||
slen = sizeof(*sa4);
|
||||
break;
|
||||
case osmo_cc_session_addrtype_ipv6:
|
||||
domain = AF_INET6;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa6 = (struct sockaddr_in6 *)&sa;
|
||||
sa6->sin6_family = domain;
|
||||
rc = inet_pton(AF_INET6, media->connection_data_local.address, &sa6->sin6_addr);
|
||||
if (rc < 1)
|
||||
goto pton_error;
|
||||
sport = &sa6->sin6_port;
|
||||
slen = sizeof(*sa6);
|
||||
break;
|
||||
case osmo_cc_session_addrtype_unknown:
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Unsupported address type '%s'.\n", media->connection_data_local.addrtype_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* rtp_port_from/rtp_port_to may be changed at run time, so rtp_port_next can become out of range. */
|
||||
if (conf->rtp_port_next < conf->rtp_port_from || conf->rtp_port_next > conf->rtp_port_to)
|
||||
conf->rtp_port_next = conf->rtp_port_from;
|
||||
start_port = conf->rtp_port_next;
|
||||
while (1) {
|
||||
/* open sockets */
|
||||
rc = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (rc < 0) {
|
||||
socket_error:
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Cannot create socket (domain=%d, errno=%d(%s))\n", domain, errno, strerror(errno));
|
||||
osmo_cc_rtp_close(media);
|
||||
return -EIO;
|
||||
}
|
||||
media->rtp_socket = rc;
|
||||
rc = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (rc < 0)
|
||||
goto socket_error;
|
||||
media->rtcp_socket = rc;
|
||||
|
||||
/* bind sockets */
|
||||
*sport = htons(conf->rtp_port_next);
|
||||
rc = bind(media->rtp_socket, (struct sockaddr *)&sa, slen);
|
||||
if (rc < 0) {
|
||||
bind_error:
|
||||
osmo_cc_rtp_close(media);
|
||||
conf->rtp_port_next = (conf->rtp_port_next + 2 > conf->rtp_port_to) ? conf->rtp_port_from : conf->rtp_port_next + 2;
|
||||
if (conf->rtp_port_next == start_port) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Cannot bind socket (errno=%d(%s))\n", errno, strerror(errno));
|
||||
return -EIO;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
*sport = htons(conf->rtp_port_next + 1);
|
||||
rc = bind(media->rtcp_socket, (struct sockaddr *)&sa, slen);
|
||||
if (rc < 0)
|
||||
goto bind_error;
|
||||
media->description.port_local = conf->rtp_port_next;
|
||||
conf->rtp_port_next = (conf->rtp_port_next + 2 > conf->rtp_port_to) ? conf->rtp_port_from : conf->rtp_port_next + 2;
|
||||
/* set nonblocking io */
|
||||
flags = fcntl(media->rtp_socket, F_GETFL);
|
||||
flags |= O_NONBLOCK;
|
||||
fcntl(media->rtp_socket, F_SETFL, flags);
|
||||
flags = fcntl(media->rtcp_socket, F_GETFL);
|
||||
flags |= O_NONBLOCK;
|
||||
fcntl(media->rtcp_socket, F_SETFL, flags);
|
||||
break;
|
||||
}
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Opening media port %d\n", media->description.port_local);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* connect RTP
|
||||
* use remote port to connect to
|
||||
*/
|
||||
int osmo_cc_rtp_connect(osmo_cc_session_media_t *media)
|
||||
{
|
||||
struct sockaddr_storage sa;
|
||||
int slen = 0; // make GCC happy
|
||||
struct sockaddr_in6 *sa6;
|
||||
struct sockaddr_in *sa4;
|
||||
uint16_t *sport;
|
||||
int rc;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Connecting media port %d->%d\n", media->description.port_local, media->description.port_remote);
|
||||
|
||||
switch (media->connection_data_remote.addrtype) {
|
||||
case osmo_cc_session_addrtype_ipv4:
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa4 = (struct sockaddr_in *)&sa;
|
||||
sa4->sin_family = AF_INET;
|
||||
rc = inet_pton(AF_INET, media->connection_data_remote.address, &sa4->sin_addr);
|
||||
if (rc < 1) {
|
||||
pton_error:
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Cannot connect to address '%s'.\n", media->connection_data_remote.address);
|
||||
return -EINVAL;
|
||||
}
|
||||
sport = &sa4->sin_port;
|
||||
slen = sizeof(*sa4);
|
||||
break;
|
||||
case osmo_cc_session_addrtype_ipv6:
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa6 = (struct sockaddr_in6 *)&sa;
|
||||
sa6->sin6_family = AF_INET6;
|
||||
rc = inet_pton(AF_INET6, media->connection_data_remote.address, &sa6->sin6_addr);
|
||||
if (rc < 1)
|
||||
goto pton_error;
|
||||
sport = &sa6->sin6_port;
|
||||
slen = sizeof(*sa6);
|
||||
break;
|
||||
case osmo_cc_session_addrtype_unknown:
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Unsupported address type '%s'.\n", media->connection_data_local.addrtype_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*sport = htons(media->description.port_remote);
|
||||
rc = connect(media->rtp_socket, (struct sockaddr *)&sa, slen);
|
||||
if (rc < 0) {
|
||||
connect_error:
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Cannot connect to address '%s'.\n", media->connection_data_remote.address);
|
||||
osmo_cc_rtp_close(media);
|
||||
return -EIO;
|
||||
}
|
||||
*sport = htons(media->description.port_remote + 1);
|
||||
rc = connect(media->rtcp_socket, (struct sockaddr *)&sa, slen);
|
||||
if (rc < 0)
|
||||
goto connect_error;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* send rtp data with given codec */
|
||||
void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, int inc_sequence, int inc_timestamp)
|
||||
{
|
||||
uint8_t *payload = NULL;
|
||||
int payload_len = 0;
|
||||
|
||||
if (!codec || !codec->media->rtp_socket)
|
||||
return;
|
||||
|
||||
if (codec->encoder)
|
||||
codec->encoder(data, len, &payload, &payload_len);
|
||||
else {
|
||||
payload = data;
|
||||
payload_len = len;
|
||||
}
|
||||
|
||||
rtp_send(codec->media->rtp_socket, payload, payload_len, codec->payload_type_remote, codec->media->tx_sequence, codec->media->tx_timestamp, codec->media->rtp_ssrc);
|
||||
codec->media->tx_sequence += inc_sequence;
|
||||
codec->media->tx_timestamp += inc_timestamp;
|
||||
|
||||
if (codec->encoder)
|
||||
free(payload);
|
||||
}
|
||||
|
||||
/* receive rtp data for given media, return < 0, if there is nothing this time */
|
||||
int osmo_cc_rtp_receive(osmo_cc_session_media_t *media)
|
||||
{
|
||||
int rc;
|
||||
uint8_t *payload = NULL;
|
||||
int payload_len = 0;
|
||||
uint8_t marker;
|
||||
uint8_t payload_type;
|
||||
osmo_cc_session_codec_t *codec;
|
||||
uint8_t *data;
|
||||
int len;
|
||||
|
||||
if (!media || media->rtp_socket <= 0)
|
||||
return -EIO;
|
||||
|
||||
rc = rtp_receive(media->rtp_socket, &payload, &payload_len, &marker, &payload_type, &media->rx_sequence, &media->rx_timestamp);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* search for codec */
|
||||
for (codec = media->codec_list; codec; codec = codec->next) {
|
||||
|
||||
if (codec->payload_type_local == payload_type)
|
||||
break;
|
||||
}
|
||||
if (!codec) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame for unknown codec (payload_type = %d).\n", payload_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (codec->decoder)
|
||||
codec->decoder(payload, payload_len, &data, &len);
|
||||
else {
|
||||
data = payload;
|
||||
len = payload_len;
|
||||
}
|
||||
|
||||
if (codec->media->receive)
|
||||
codec->media->receiver(codec, media->rx_sequence, media->rx_timestamp, data, len);
|
||||
|
||||
if (codec->decoder)
|
||||
free(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void osmo_cc_rtp_close(osmo_cc_session_media_t *media)
|
||||
{
|
||||
if (media->rtp_socket) {
|
||||
close(media->rtp_socket);
|
||||
media->rtp_socket = 0;
|
||||
}
|
||||
if (media->rtcp_socket) {
|
||||
close(media->rtcp_socket);
|
||||
media->rtcp_socket = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
|
||||
void osmo_cc_set_rtp_ports(osmo_cc_session_config_t *conf, uint16_t from, uint16_t to);
|
||||
int osmo_cc_rtp_open(osmo_cc_session_media_t *media);
|
||||
int osmo_cc_rtp_connect(osmo_cc_session_media_t *media);
|
||||
void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, int inc_sequence, int inc_timestamp);
|
||||
int osmo_cc_rtp_receive(osmo_cc_session_media_t *media);
|
||||
void osmo_cc_rtp_close(osmo_cc_session_media_t *media);
|
||||
|
|
@ -1,684 +0,0 @@
|
|||
/* Endpoint and call process handling
|
||||
*
|
||||
* (C) 2019 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 <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include "../libtimer/timer.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "endpoint.h"
|
||||
#include "message.h"
|
||||
|
||||
#define SCREEN_QUESTIONMARK 1
|
||||
#define SCREEN_STAR 2
|
||||
#define SCREEN_AT 3
|
||||
|
||||
void osmo_cc_help_screen(void)
|
||||
{
|
||||
printf("Screening options:\n\n");
|
||||
|
||||
printf("screen-calling-in [attrs] <current caller ID> [attrs] <new caller ID>\n");
|
||||
printf("screen-called-in [attrs] <current dialed number> [attrs] <new dialed number>\n");
|
||||
printf("screen-calling-out [attrs] <current caller ID> [attrs] <new caller ID>\n");
|
||||
printf("screen-called-out [attrs] <current dialed number> [attrs] <new dialed number>\n\n");
|
||||
|
||||
printf("These options allow to screen an incoming or outgoing caller ID or dialed\n");
|
||||
printf("number. If 'the current caller ID' or 'current dialed number' matches, it will\n");
|
||||
printf("be replaced by 'new caller ID' or 'new dialed number'. 'incoming' means from\n");
|
||||
printf(" the interface and 'outgoing' means towards the interface.\n\n");
|
||||
|
||||
printf("Attributes prior 'current caller ID' or 'new dialed number' may be used to\n");
|
||||
printf("perform screening only if the attribute match. Attributes prior\n");
|
||||
printf("'new caller ID' or 'new dialed number' may be used to alter them. Attribute to\n");
|
||||
printf("define the type of number can be: 'unknown', 'international', 'national',\n");
|
||||
printf("'network', 'subscriber', 'abbreviated' Attribute to define the restriction of a\n");
|
||||
printf("caller ID: 'allowed', 'restricted'\n\n");
|
||||
|
||||
printf("The current caller ID or dialed number may contain one or more '?', to allow\n");
|
||||
printf("any digit to match. The current caller ID or dialed number may contain a '*',\n");
|
||||
printf("to allow any suffix to match from now on. The new caller ID or dialed number\n");
|
||||
printf("may contain a '*', to append the suffix from the current caller ID or dialed\n");
|
||||
printf("number.\n\n");
|
||||
}
|
||||
|
||||
char *osmo_cc_strtok_quotes(const char **text_p)
|
||||
{
|
||||
static char token[1024];
|
||||
const char *text = *text_p;
|
||||
int i, quote;
|
||||
|
||||
/* skip spaces */
|
||||
while (*text) {
|
||||
if (*text > 32)
|
||||
break;
|
||||
text++;
|
||||
}
|
||||
|
||||
/* if eol, return NULL */
|
||||
if (!(*text))
|
||||
return NULL;
|
||||
|
||||
i = 0;
|
||||
quote = 0;
|
||||
while (*text) {
|
||||
/* escape allows all following characters */
|
||||
if (*text == '\\') {
|
||||
text++;
|
||||
if (*text)
|
||||
token[i++] = *text++;
|
||||
continue;
|
||||
}
|
||||
/* no quote, check for them or break on white space */
|
||||
if (quote == 0) {
|
||||
if (*text == '\'') {
|
||||
quote = 1;
|
||||
text++;
|
||||
continue;
|
||||
}
|
||||
if (*text == '\"') {
|
||||
quote = 2;
|
||||
text++;
|
||||
continue;
|
||||
}
|
||||
if (*text <= ' ')
|
||||
break;
|
||||
}
|
||||
/* single quote, check for unquote */
|
||||
if (quote == 1 && *text == '\'') {
|
||||
quote = 0;
|
||||
text++;
|
||||
continue;
|
||||
}
|
||||
/* double quote, check for unquote */
|
||||
if (quote == 2 && *text == '\"') {
|
||||
quote = 0;
|
||||
text++;
|
||||
continue;
|
||||
}
|
||||
/* copy character */
|
||||
token[i++] = *text++;
|
||||
}
|
||||
token[i] = '\0';
|
||||
|
||||
*text_p = text;
|
||||
return token;
|
||||
}
|
||||
|
||||
int osmo_cc_add_screen(osmo_cc_endpoint_t *ep, const char *text)
|
||||
{
|
||||
osmo_cc_screen_list_t **list_p = NULL, *list;
|
||||
const char *token;
|
||||
int no_present = 0, calling_in = 0, star_used, at_used;
|
||||
int i, j;
|
||||
|
||||
star_used = 0;
|
||||
if (!strncasecmp(text, "screen-calling-in", 17)) {
|
||||
text += 17;
|
||||
list_p = &ep->screen_calling_in;
|
||||
no_present = 1;
|
||||
calling_in = 1;
|
||||
} else if (!strncasecmp(text, "screen-called-in", 16)) {
|
||||
text += 16;
|
||||
list_p = &ep->screen_called_in;
|
||||
} else if (!strncasecmp(text, "screen-calling-out", 18)) {
|
||||
text += 18;
|
||||
list_p = &ep->screen_calling_out;
|
||||
no_present = 1;
|
||||
} else if (!strncasecmp(text, "screen-called-out", 17)) {
|
||||
text += 17;
|
||||
list_p = &ep->screen_called_out;
|
||||
} else {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Invalid screening definition \"%s\". It must start with 'screen-calling-in' or 'screen-called-in' or 'screen-calling-out' or 'screen-called-out'\n", text);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* skip space behind screen list string */
|
||||
while (*text) {
|
||||
if (*text > 32)
|
||||
break;
|
||||
text++;
|
||||
}
|
||||
|
||||
list = calloc(1, sizeof(*list));
|
||||
if (!list)
|
||||
return -ENOMEM;
|
||||
|
||||
next_from:
|
||||
token = osmo_cc_strtok_quotes(&text);
|
||||
if (!token) {
|
||||
free(list);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Missing 'from' string in screening definition \"%s\". If the string shall be empty, use double quotes. (\'\' or \"\")\n", text);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!strcasecmp(token, "unknown")) {
|
||||
list->has_from_type = 1;
|
||||
list->from_type = OSMO_CC_TYPE_UNKNOWN;
|
||||
goto next_from;
|
||||
} else
|
||||
if (!strcasecmp(token, "international")) {
|
||||
list->has_from_type = 1;
|
||||
list->from_type = OSMO_CC_TYPE_INTERNATIONAL;
|
||||
goto next_from;
|
||||
} else
|
||||
if (!strcasecmp(token, "national")) {
|
||||
list->has_from_type = 1;
|
||||
list->from_type = OSMO_CC_TYPE_NATIONAL;
|
||||
goto next_from;
|
||||
} else
|
||||
if (!strcasecmp(token, "network")) {
|
||||
list->has_from_type = 1;
|
||||
list->from_type = OSMO_CC_TYPE_NETWORK;
|
||||
goto next_from;
|
||||
} else
|
||||
if (!strcasecmp(token, "subscriber")) {
|
||||
list->has_from_type = 1;
|
||||
list->from_type = OSMO_CC_TYPE_SUBSCRIBER;
|
||||
goto next_from;
|
||||
} else
|
||||
if (!strcasecmp(token, "abbreviated")) {
|
||||
list->has_from_type = 1;
|
||||
list->from_type = OSMO_CC_TYPE_ABBREVIATED;
|
||||
goto next_from;
|
||||
} else
|
||||
if (!strcasecmp(token, "allowed")) {
|
||||
if (no_present) {
|
||||
no_present_error:
|
||||
free(list);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Keyword '%s' not allowed in screen entry for called number\n", token);
|
||||
return -EINVAL;
|
||||
}
|
||||
list->has_from_present = 1;
|
||||
list->from_present = OSMO_CC_PRESENT_ALLOWED;
|
||||
goto next_from;
|
||||
} else
|
||||
if (!strcasecmp(token, "restricted")) {
|
||||
if (no_present)
|
||||
goto no_present_error;
|
||||
list->has_from_present = 1;
|
||||
list->from_present = OSMO_CC_PRESENT_RESTRICTED;
|
||||
goto next_from;
|
||||
} else {
|
||||
for (i = j = 0; token[i] && j < (int)sizeof(list->from) - 1; i++, j++) {
|
||||
if (token[i] == '?')
|
||||
list->from[j] = SCREEN_QUESTIONMARK;
|
||||
else
|
||||
if (token[i] == '*') {
|
||||
if (star_used) {
|
||||
free(list);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "The '*' may be used only once.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
list->from[j] = SCREEN_STAR;
|
||||
star_used = 1;
|
||||
} else
|
||||
if (token[i] == '\\' && token[i + 1] != '\0')
|
||||
list->from[j] = token[++i];
|
||||
else
|
||||
list->from[j] = token[i];
|
||||
}
|
||||
list->from[j] = '\0';
|
||||
}
|
||||
|
||||
star_used = 0;
|
||||
next_to:
|
||||
token = osmo_cc_strtok_quotes(&text);
|
||||
if (!token) {
|
||||
free(list);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Missing screening result. If the string shall be empty, use double quotes. (\'\' or \"\")\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!strcasecmp(token, "unknown")) {
|
||||
list->has_to_type = 1;
|
||||
list->to_type = OSMO_CC_TYPE_UNKNOWN;
|
||||
goto next_to;
|
||||
} else
|
||||
if (!strcasecmp(token, "international")) {
|
||||
list->has_to_type = 1;
|
||||
list->to_type = OSMO_CC_TYPE_INTERNATIONAL;
|
||||
goto next_to;
|
||||
} else
|
||||
if (!strcasecmp(token, "national")) {
|
||||
list->has_to_type = 1;
|
||||
list->to_type = OSMO_CC_TYPE_NATIONAL;
|
||||
goto next_to;
|
||||
} else
|
||||
if (!strcasecmp(token, "network")) {
|
||||
list->has_to_type = 1;
|
||||
list->to_type = OSMO_CC_TYPE_NETWORK;
|
||||
goto next_to;
|
||||
} else
|
||||
if (!strcasecmp(token, "subscriber")) {
|
||||
list->has_to_type = 1;
|
||||
list->to_type = OSMO_CC_TYPE_SUBSCRIBER;
|
||||
goto next_to;
|
||||
} else
|
||||
if (!strcasecmp(token, "abbreviated")) {
|
||||
list->has_to_type = 1;
|
||||
list->to_type = OSMO_CC_TYPE_ABBREVIATED;
|
||||
goto next_to;
|
||||
} else
|
||||
if (!strcasecmp(token, "allowed")) {
|
||||
if (no_present)
|
||||
goto no_present_error;
|
||||
list->has_to_present = 1;
|
||||
list->to_present = OSMO_CC_PRESENT_ALLOWED;
|
||||
goto next_to;
|
||||
} else
|
||||
if (!strcasecmp(token, "restricted")) {
|
||||
if (no_present)
|
||||
goto no_present_error;
|
||||
list->has_to_present = 1;
|
||||
list->to_present = OSMO_CC_PRESENT_RESTRICTED;
|
||||
goto next_to;
|
||||
} else {
|
||||
for (i = j = 0; token[i] && j < (int)sizeof(list->to) - 1; i++, j++) {
|
||||
if (token[i] == '*') {
|
||||
if (star_used) {
|
||||
free(list);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "The '*' may be used only once.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
list->to[j] = SCREEN_STAR;
|
||||
star_used = 1;
|
||||
} else
|
||||
if (token[i] == '@') {
|
||||
if (!calling_in) {
|
||||
free(list);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "The '@' may be used only for incoming calls from interface.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (at_used) {
|
||||
free(list);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "The '@' may be used only once.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
list->to[j] = SCREEN_AT;
|
||||
at_used = 1;
|
||||
} else
|
||||
if (token[i] == '\\' && token[i + 1] != '\0')
|
||||
list->to[j] = token[++i];
|
||||
else
|
||||
list->to[j] = token[i];
|
||||
}
|
||||
list->to[j] = '\0';
|
||||
}
|
||||
|
||||
token = osmo_cc_strtok_quotes(&text);
|
||||
if (token) {
|
||||
free(list);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text);
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Got garbage behind screening result.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* attach screen entry to list */
|
||||
while (*list_p)
|
||||
list_p = &((*list_p)->next);
|
||||
*list_p = list;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void osmo_cc_flush_screen(osmo_cc_screen_list_t *list)
|
||||
{
|
||||
osmo_cc_screen_list_t *temp;
|
||||
|
||||
while (list) {
|
||||
temp = list;
|
||||
list = list->next;
|
||||
free(temp);
|
||||
}
|
||||
}
|
||||
|
||||
const char *print_rule_string(const char *input)
|
||||
{
|
||||
static char output[256];
|
||||
int i;
|
||||
|
||||
for (i = 0; *input && i < (int)sizeof(output) - 1; i++, input++) {
|
||||
switch (*input) {
|
||||
case SCREEN_QUESTIONMARK:
|
||||
output[i] = '?';
|
||||
break;
|
||||
case SCREEN_STAR:
|
||||
output[i] = '*';
|
||||
break;
|
||||
case SCREEN_AT:
|
||||
output[i] = '@';
|
||||
break;
|
||||
default:
|
||||
output[i] = *input;
|
||||
}
|
||||
}
|
||||
|
||||
output[i] = '\0';
|
||||
return output;
|
||||
}
|
||||
|
||||
static int osmo_cc_screen(const char *what, osmo_cc_screen_list_t *list, uint8_t *type, uint8_t *present, char *id_to, int id_to_size, const char *id_from, const char **routing_p)
|
||||
{
|
||||
const char *suffix;
|
||||
int i, j, rule;
|
||||
|
||||
PDEBUG(DCC, DEBUG_INFO, "Screening %s '%s':\n", what, id_from);
|
||||
switch (*type) {
|
||||
case OSMO_CC_TYPE_UNKNOWN:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = unknown\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_INTERNATIONAL:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = international\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_NATIONAL:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = national\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_NETWORK:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = network\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_SUBSCRIBER:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = subscriber\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_ABBREVIATED:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = abbreviated\n");
|
||||
break;
|
||||
}
|
||||
if (present) switch (*present) {
|
||||
case OSMO_CC_PRESENT_ALLOWED:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> present = allowed\n");
|
||||
break;
|
||||
case OSMO_CC_PRESENT_RESTRICTED:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> present = restricted\n");
|
||||
break;
|
||||
}
|
||||
|
||||
rule = 0;
|
||||
while (list) {
|
||||
rule++;
|
||||
PDEBUG(DCC, DEBUG_INFO, "Comparing with rule #%d: '%s':\n", rule, print_rule_string(list->from));
|
||||
if (list->has_from_type) switch (list->from_type) {
|
||||
case OSMO_CC_TYPE_UNKNOWN:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = unknown\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_INTERNATIONAL:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = international\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_NATIONAL:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = national\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_NETWORK:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = network\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_SUBSCRIBER:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = subscriber\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_ABBREVIATED:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = abbreviated\n");
|
||||
break;
|
||||
}
|
||||
if (list->has_from_present) switch (list->from_present) {
|
||||
case OSMO_CC_PRESENT_ALLOWED:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> present = allowed\n");
|
||||
break;
|
||||
case OSMO_CC_PRESENT_RESTRICTED:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> present = restricted\n");
|
||||
break;
|
||||
}
|
||||
suffix = NULL;
|
||||
/* attributes do not match */
|
||||
if (list->has_from_type && list->from_type != *type) {
|
||||
PDEBUG(DCC, DEBUG_INFO, "Rule does not match, because 'type' is different.\n");
|
||||
continue;
|
||||
}
|
||||
if (present && list->has_from_present && list->from_present != *present) {
|
||||
PDEBUG(DCC, DEBUG_INFO, "Rule does not match, because 'present' is different.\n");
|
||||
continue;
|
||||
}
|
||||
for (i = 0; list->from[i] && id_from[i]; i++) {
|
||||
/* '?' means: any digit, so it machtes */
|
||||
if (list->from[i] == SCREEN_QUESTIONMARK) {
|
||||
continue;
|
||||
}
|
||||
/* '*' means: anything may follow, so it machtes */
|
||||
if (list->from[i] == SCREEN_STAR) {
|
||||
suffix = id_from + i;
|
||||
break;
|
||||
}
|
||||
/* check if digit doesn't matches */
|
||||
if (list->from[i] != id_from[i])
|
||||
break;
|
||||
}
|
||||
/* if last checked digit is '*', we have a match */
|
||||
/* also if we hit EOL at id_from and next check digit is '*' */
|
||||
if (list->from[i] == SCREEN_STAR)
|
||||
break;
|
||||
/* if all digits have matched */
|
||||
if (list->from[i] == '\0' && id_from[i] == '\0')
|
||||
break;
|
||||
PDEBUG(DCC, DEBUG_INFO, "Rule does not match, because %s is different.\n", what);
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
/* if no list entry matches */
|
||||
if (!list)
|
||||
return -1;
|
||||
|
||||
/* replace ID */
|
||||
if (list->has_to_type) {
|
||||
*type = list->to_type;
|
||||
}
|
||||
if (present && list->has_to_present) {
|
||||
*present = list->to_present;
|
||||
}
|
||||
for (i = j = 0; list->to[i]; i++) {
|
||||
if (j == id_to_size - 1)
|
||||
break;
|
||||
/* '*' means to use suffix of input string */
|
||||
if (list->to[i] == SCREEN_STAR && suffix) {
|
||||
while (*suffix) {
|
||||
id_to[j++] = *suffix++;
|
||||
if (j == id_to_size - 1)
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
/* '@' means to stop and return routing also */
|
||||
} else if (list->to[i] == SCREEN_AT) {
|
||||
*routing_p = &list->to[i];
|
||||
break;
|
||||
}
|
||||
/* copy output digit */
|
||||
id_to[j++] = list->to[i];
|
||||
}
|
||||
id_to[j] = '\0';
|
||||
|
||||
PDEBUG(DCC, DEBUG_INFO, "Rule matches, changing %s to '%s'.\n", what, print_rule_string(id_to));
|
||||
if (list->has_to_type) switch (list->to_type) {
|
||||
case OSMO_CC_TYPE_UNKNOWN:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = unknown\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_INTERNATIONAL:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = international\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_NATIONAL:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = national\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_NETWORK:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = network\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_SUBSCRIBER:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = subscriber\n");
|
||||
break;
|
||||
case OSMO_CC_TYPE_ABBREVIATED:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> type = abbreviated\n");
|
||||
break;
|
||||
}
|
||||
if (list->has_to_present) switch (list->to_present) {
|
||||
case OSMO_CC_PRESENT_ALLOWED:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> present = allowed\n");
|
||||
break;
|
||||
case OSMO_CC_PRESENT_RESTRICTED:
|
||||
PDEBUG(DCC, DEBUG_INFO, " -> present = restricted\n");
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
osmo_cc_msg_t *osmo_cc_screen_msg(osmo_cc_endpoint_t *ep, osmo_cc_msg_t *old_msg, int in, const char **routing_p)
|
||||
{
|
||||
osmo_cc_msg_t *new_msg;
|
||||
char id[256], calling[256], called[256], redir[256];
|
||||
uint8_t calling_type, calling_plan, calling_present, calling_screen;
|
||||
uint8_t called_type, called_plan;
|
||||
uint8_t redir_type, redir_plan, redir_present, redir_screen, redir_reason;
|
||||
int calling_status = 0, called_status = 0, redir_status = 0;
|
||||
int rc;
|
||||
void *ie, *to_ie;
|
||||
uint8_t ie_type;
|
||||
uint16_t ie_length;
|
||||
void *ie_value;
|
||||
|
||||
if (in && ep->screen_calling_in) {
|
||||
rc = osmo_cc_get_ie_calling(old_msg, 0, &calling_type, &calling_plan, &calling_present, &calling_screen, id, sizeof(id));
|
||||
if (rc >= 0) {
|
||||
rc = osmo_cc_screen("incoming caller ID", ep->screen_calling_in, &calling_type, &calling_present, calling, sizeof(calling), id, routing_p);
|
||||
if (rc >= 0)
|
||||
calling_status = 1;
|
||||
} else {
|
||||
calling_type = OSMO_CC_TYPE_UNKNOWN;
|
||||
calling_plan = OSMO_CC_PLAN_TELEPHONY;
|
||||
calling_present = OSMO_CC_PRESENT_ALLOWED;
|
||||
calling_screen = OSMO_CC_SCREEN_NETWORK;
|
||||
rc = osmo_cc_screen("incoming caller ID", ep->screen_calling_in, &calling_type, &calling_present, calling, sizeof(calling), "", routing_p);
|
||||
if (rc >= 0)
|
||||
calling_status = 1;
|
||||
}
|
||||
rc = osmo_cc_get_ie_redir(old_msg, 0, &redir_type, &redir_plan, &redir_present, &redir_screen, &redir_reason, id, sizeof(id));
|
||||
if (rc >= 0) {
|
||||
rc = osmo_cc_screen("incoming redirecting number", ep->screen_calling_in, &redir_type, &redir_present, redir, sizeof(redir), id, NULL);
|
||||
if (rc >= 0)
|
||||
redir_status = 1;
|
||||
}
|
||||
}
|
||||
if (in && ep->screen_called_in) {
|
||||
rc = osmo_cc_get_ie_called(old_msg, 0, &called_type, &called_plan, id, sizeof(id));
|
||||
if (rc >= 0) {
|
||||
rc = osmo_cc_screen("incoming dialed number", ep->screen_called_in, &called_type, NULL, called, sizeof(called), id, NULL);
|
||||
if (rc >= 0)
|
||||
called_status = 1;
|
||||
} else {
|
||||
called_type = OSMO_CC_TYPE_UNKNOWN;
|
||||
called_plan = OSMO_CC_PLAN_TELEPHONY;
|
||||
rc = osmo_cc_screen("incoming dialed number", ep->screen_called_in, &called_type, NULL, called, sizeof(called), "", NULL);
|
||||
if (rc >= 0)
|
||||
called_status = 1;
|
||||
}
|
||||
}
|
||||
if (!in && ep->screen_calling_out) {
|
||||
rc = osmo_cc_get_ie_calling(old_msg, 0, &calling_type, &calling_plan, &calling_present, &calling_screen, id, sizeof(id));
|
||||
if (rc >= 0) {
|
||||
rc = osmo_cc_screen("outgoing caller ID", ep->screen_calling_out, &calling_type, &calling_present, calling, sizeof(calling), id, NULL);
|
||||
if (rc >= 0)
|
||||
calling_status = 1;
|
||||
} else {
|
||||
calling_type = OSMO_CC_TYPE_UNKNOWN;
|
||||
calling_plan = OSMO_CC_PLAN_TELEPHONY;
|
||||
calling_present = OSMO_CC_PRESENT_ALLOWED;
|
||||
calling_screen = OSMO_CC_SCREEN_NETWORK;
|
||||
rc = osmo_cc_screen("outgoing caller ID", ep->screen_calling_out, &calling_type, &calling_present, calling, sizeof(calling), "", NULL);
|
||||
if (rc >= 0)
|
||||
calling_status = 1;
|
||||
}
|
||||
rc = osmo_cc_get_ie_redir(old_msg, 0, &redir_type, &redir_plan, &redir_present, &redir_screen, &redir_reason, id, sizeof(id));
|
||||
if (rc >= 0) {
|
||||
rc = osmo_cc_screen("outgoing redirecting number", ep->screen_calling_out, &redir_type, &redir_present, redir, sizeof(redir), id, NULL);
|
||||
if (rc >= 0)
|
||||
redir_status = 1;
|
||||
}
|
||||
}
|
||||
if (!in && ep->screen_called_out) {
|
||||
rc = osmo_cc_get_ie_called(old_msg, 0, &called_type, &called_plan, id, sizeof(id));
|
||||
if (rc >= 0) {
|
||||
rc = osmo_cc_screen("outgoing dialed number", ep->screen_called_out, &called_type, NULL, called, sizeof(called), id, NULL);
|
||||
if (rc >= 0)
|
||||
called_status = 1;
|
||||
} else {
|
||||
called_type = OSMO_CC_TYPE_UNKNOWN;
|
||||
called_plan = OSMO_CC_PLAN_TELEPHONY;
|
||||
rc = osmo_cc_screen("outgoing dialed number", ep->screen_called_out, &called_type, NULL, called, sizeof(called), "", NULL);
|
||||
if (rc >= 0)
|
||||
called_status = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* nothing screened */
|
||||
if (!calling_status && !called_status && !redir_status)
|
||||
return old_msg;
|
||||
|
||||
new_msg = osmo_cc_new_msg(old_msg->type);
|
||||
|
||||
/* copy and replace */
|
||||
ie = old_msg->data;
|
||||
while ((ie_value = osmo_cc_msg_sep_ie(old_msg, &ie, &ie_type, &ie_length))) {
|
||||
switch (ie_type) {
|
||||
case OSMO_CC_IE_CALLING:
|
||||
if (calling_status) {
|
||||
osmo_cc_add_ie_calling(new_msg, calling_type, calling_plan, calling_present, calling_screen, calling);
|
||||
calling_status = 0;
|
||||
break;
|
||||
}
|
||||
goto copy;
|
||||
case OSMO_CC_IE_CALLED:
|
||||
if (called_status) {
|
||||
osmo_cc_add_ie_called(new_msg, called_type, called_plan, called);
|
||||
called_status = 0;
|
||||
break;
|
||||
}
|
||||
goto copy;
|
||||
case OSMO_CC_IE_REDIR:
|
||||
if (redir_status) {
|
||||
osmo_cc_add_ie_redir(new_msg, redir_type, redir_plan, redir_present, redir_screen, redir_reason, redir);
|
||||
redir_status = 0;
|
||||
break;
|
||||
}
|
||||
goto copy;
|
||||
default:
|
||||
copy:
|
||||
to_ie = osmo_cc_add_ie(new_msg, ie_type, ie_length);
|
||||
memcpy(to_ie, ie_value, ie_length);
|
||||
}
|
||||
}
|
||||
|
||||
/* applend, if not yet in message (except redir, since it must exist) */
|
||||
if (calling_status)
|
||||
osmo_cc_add_ie_calling(new_msg, calling_type, calling_plan, calling_present, calling_screen, calling);
|
||||
if (called_status)
|
||||
osmo_cc_add_ie_called(new_msg, called_type, called_plan, called);
|
||||
|
||||
free(old_msg);
|
||||
return new_msg;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
void osmo_cc_help_screen(void);
|
||||
char *osmo_cc_strtok_quotes(const char **text_p);
|
||||
int osmo_cc_add_screen(osmo_cc_endpoint_t *ep, const char *text);
|
||||
void osmo_cc_flush_screen(osmo_cc_screen_list_t *list);
|
||||
osmo_cc_msg_t *osmo_cc_screen_msg(osmo_cc_endpoint_t *ep, osmo_cc_msg_t *old_msg, int in, const char **routing_p);
|
||||
|
|
@ -1,544 +0,0 @@
|
|||
/* Session Description Protocol parsing and generator
|
||||
* This shall be simple and is incomplete.
|
||||
*
|
||||
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libtimer/timer.h"
|
||||
#include "endpoint.h"
|
||||
#include "sdp.h"
|
||||
|
||||
#define strncat_printf(sdp, fmt, arg...) \
|
||||
{ \
|
||||
snprintf(sdp + strlen(sdp), sizeof(sdp) - strlen(sdp), fmt, ## arg); \
|
||||
sdp[sizeof(sdp) - 1] = '\0'; \
|
||||
}
|
||||
|
||||
/* generate SDP from session structure */
|
||||
char *osmo_cc_session_gensdp(osmo_cc_session_t *session)
|
||||
{
|
||||
/* calc max size of SDP: quick an dirty (close to max UDP payload size) */
|
||||
static char sdp[65000];
|
||||
const char *username, *sess_id, *sess_version, *nettype, *addrtype, *unicast_address;
|
||||
const char *session_name;
|
||||
int individual_connection_data = 1; /* in case there is no media, there is no connection data */
|
||||
int individual_send_receive = 1; /* in case there is no media, there is no send/receive attribute */
|
||||
struct osmo_cc_session_media *media;
|
||||
struct osmo_cc_session_codec *codec;
|
||||
|
||||
sdp[0] = 0;
|
||||
|
||||
/* Version */
|
||||
strncat_printf(sdp, "v=0\r\n");
|
||||
|
||||
/* Origin */
|
||||
username = session->origin_local.username;
|
||||
sess_id = session->origin_local.sess_id;
|
||||
sess_version = session->origin_local.sess_version;
|
||||
nettype = session->origin_local.nettype;
|
||||
addrtype = session->origin_local.addrtype;
|
||||
unicast_address = session->origin_local.unicast_address;
|
||||
strncat_printf(sdp, "o=%s %s %s %s %s %s\r\n", username, sess_id, sess_version, nettype, addrtype, unicast_address);
|
||||
|
||||
/* Session */
|
||||
session_name = session->name;
|
||||
strncat_printf(sdp, "s=%s\r\n", session_name);
|
||||
|
||||
/* Connection Data (if all media have the same data) */
|
||||
if (session->media_list) {
|
||||
osmo_cc_session_for_each_media(session->media_list->next, media) {
|
||||
if (session->media_list->connection_data_local.nettype != media->connection_data_local.nettype)
|
||||
break;
|
||||
if (session->media_list->connection_data_local.addrtype != media->connection_data_local.addrtype)
|
||||
break;
|
||||
if (!!strcmp(session->media_list->connection_data_local.address, media->connection_data_local.address))
|
||||
break;
|
||||
}
|
||||
if (!media)
|
||||
individual_connection_data = 0;
|
||||
}
|
||||
if (!individual_connection_data)
|
||||
strncat_printf(sdp, "c=%s %s %s\r\n", osmo_cc_session_nettype2string(session->media_list->connection_data_local.nettype), osmo_cc_session_addrtype2string(session->media_list->connection_data_local.addrtype), session->media_list->connection_data_local.address);
|
||||
|
||||
/* timestamp */
|
||||
strncat_printf(sdp, "t=0 0\r\n");
|
||||
|
||||
/* sendonly /recvonly (if all media have the same data) */
|
||||
if (session->media_list) {
|
||||
osmo_cc_session_for_each_media(session->media_list->next, media) {
|
||||
if (session->media_list->send != media->send)
|
||||
break;
|
||||
if (session->media_list->receive != media->receive)
|
||||
break;
|
||||
}
|
||||
if (!media)
|
||||
individual_send_receive = 0;
|
||||
}
|
||||
if (!individual_send_receive) {
|
||||
if (session->media_list->send && !session->media_list->receive)
|
||||
strncat_printf(sdp, "a=sendonly\r\n");
|
||||
if (!session->media_list->send && session->media_list->receive)
|
||||
strncat_printf(sdp, "a=recvonly\r\n");
|
||||
if (!session->media_list->send && !session->media_list->receive)
|
||||
strncat_printf(sdp, "a=inactive\r\n");
|
||||
}
|
||||
|
||||
/* media */
|
||||
osmo_cc_session_for_each_media(session->media_list, media) {
|
||||
strncat_printf(sdp, "m=%s %u %s",
|
||||
osmo_cc_session_media_type2string(media->description.type) ? : media->description.type_name,
|
||||
media->description.port_local,
|
||||
osmo_cc_session_media_proto2string(media->description.proto) ? : media->description.proto_name);
|
||||
osmo_cc_session_for_each_codec(media->codec_list, codec)
|
||||
strncat_printf(sdp, " %u", codec->payload_type_local);
|
||||
strncat_printf(sdp, "\r\n");
|
||||
/* don't list rtpmap when session was canceled by setting port to 0 */
|
||||
if (media->description.port_local == 0)
|
||||
continue;
|
||||
if (individual_connection_data)
|
||||
strncat_printf(sdp, "c=%s %s %s\r\n", osmo_cc_session_nettype2string(media->connection_data_local.nettype), osmo_cc_session_addrtype2string(media->connection_data_local.addrtype), media->connection_data_local.address);
|
||||
osmo_cc_session_for_each_codec(media->codec_list, codec) {
|
||||
strncat_printf(sdp, "a=rtpmap:%u %s/%d", codec->payload_type_local, codec->payload_name, codec->payload_rate);
|
||||
if (codec->payload_channels >= 2)
|
||||
strncat_printf(sdp, "/%d", codec->payload_channels);
|
||||
strncat_printf(sdp, "\r\n");
|
||||
}
|
||||
if (individual_send_receive) {
|
||||
if (media->send && !media->receive)
|
||||
strncat_printf(sdp, "a=sendonly\r\n");
|
||||
if (!media->send && media->receive)
|
||||
strncat_printf(sdp, "a=recvonly\r\n");
|
||||
if (!media->send && !media->receive)
|
||||
strncat_printf(sdp, "a=inactive\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* check for overflow and return */
|
||||
if (strlen(sdp) == sizeof(sdp) - 1) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Fatal error: Allocated SDP buffer with %d bytes is too small, please fix!\n", (int)sizeof(sdp));
|
||||
return NULL;
|
||||
}
|
||||
return sdp;
|
||||
}
|
||||
|
||||
/* separate a word from string that is delimited with one or more space characters */
|
||||
static char *wordsep(char **text_p)
|
||||
{
|
||||
char *text = *text_p;
|
||||
static char word[256];
|
||||
int i;
|
||||
|
||||
/* no text */
|
||||
if (text == NULL || *text == '\0')
|
||||
return NULL;
|
||||
/* skip spaces before text */
|
||||
while (*text && *text <= ' ')
|
||||
text++;
|
||||
/* copy content */
|
||||
i = 0;
|
||||
while (*text > ' ' && i < (int)sizeof(word))
|
||||
word[i++] = *text++;
|
||||
word[i] = '\0';
|
||||
/* set next */
|
||||
*text_p = text;
|
||||
return word;
|
||||
}
|
||||
|
||||
/*
|
||||
* codecs and their default values
|
||||
*
|
||||
* if format is -1, payload type is dynamic
|
||||
* if rate is 0, rate may be any rate
|
||||
*/
|
||||
struct codec_defaults {
|
||||
int fmt;
|
||||
char *name;
|
||||
uint32_t rate;
|
||||
int channels;
|
||||
} codec_defaults[] = {
|
||||
{ 0, "PCMU", 8000, 1 },
|
||||
{ 3, "GSM", 8000, 1 },
|
||||
{ 4, "G723", 8000, 1 },
|
||||
{ 5, "DVI4", 8000, 1 },
|
||||
{ 6, "DVI4", 16000, 1 },
|
||||
{ 7, "LPC", 8000, 1 },
|
||||
{ 8, "PCMA", 8000, 1 },
|
||||
{ 9, "G722", 8000, 1 },
|
||||
{ 10, "L16", 44100, 2 },
|
||||
{ 11, "L16", 44100, 1 },
|
||||
{ 12, "QCELP", 8000, 1 },
|
||||
{ 13, "CN", 8000, 1 },
|
||||
{ 14, "MPA", 90000, 1 },
|
||||
{ 15, "G728", 8000, 1 },
|
||||
{ 16, "DVI4", 11025, 1 },
|
||||
{ 17, "DVI4", 22050, 1 },
|
||||
{ 18, "G729", 8000, 1 },
|
||||
{ 25, "CELB", 90000, 0 },
|
||||
{ 26, "JPEG", 90000, 0 },
|
||||
{ 28, "nv", 90000, 0 },
|
||||
{ 31, "H261", 90000, 0 },
|
||||
{ 32, "MPV", 90000, 0 },
|
||||
{ 33, "MP2T", 90000, 0 },
|
||||
{ 34, "H263", 90000, 0 },
|
||||
{ -1, NULL, 0, 0 },
|
||||
};
|
||||
|
||||
static void complete_codec_by_fmt(uint8_t fmt, const char **name, uint32_t *rate, int *channels)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; codec_defaults[i].name; i++) {
|
||||
if (codec_defaults[i].fmt == fmt)
|
||||
break;
|
||||
}
|
||||
if (!codec_defaults[i].name)
|
||||
return;
|
||||
|
||||
free((char *)*name);
|
||||
*name = strdup(codec_defaults[i].name);
|
||||
*rate = codec_defaults[i].rate;
|
||||
*channels = codec_defaults[i].channels;
|
||||
}
|
||||
|
||||
int osmo_cc_payload_type_by_attrs(uint8_t *fmt, const char *name, uint32_t *rate, int *channels)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; codec_defaults[i].name; i++) {
|
||||
if (!strcmp(codec_defaults[i].name, name)
|
||||
&& (*rate == 0 || codec_defaults[i].rate == *rate)
|
||||
&& (*channels == 0 || codec_defaults[i].channels == *channels))
|
||||
break;
|
||||
}
|
||||
if (!codec_defaults[i].name)
|
||||
return -EINVAL;
|
||||
|
||||
*fmt = codec_defaults[i].fmt;
|
||||
*rate = codec_defaults[i].rate;
|
||||
*channels = codec_defaults[i].channels;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* parses data and codec list from SDP
|
||||
*
|
||||
* sdp = given SDP text
|
||||
* return: SDP session description structure */
|
||||
struct osmo_cc_session *osmo_cc_session_parsesdp(osmo_cc_session_config_t *conf, void *priv, const char *_sdp)
|
||||
{
|
||||
char buffer[strlen(_sdp) + 1], *sdp = buffer;
|
||||
char *line, *p, *word, *next_word;
|
||||
int line_no = 0;
|
||||
struct osmo_cc_session_connection_data ccd, *cd;
|
||||
int csend = 1, creceive = 1; /* common default */
|
||||
struct osmo_cc_session *session = NULL;
|
||||
struct osmo_cc_session_media *media = NULL;
|
||||
struct osmo_cc_session_codec *codec = NULL;
|
||||
|
||||
/* prepare data */
|
||||
strcpy(sdp, _sdp);
|
||||
memset(&ccd, 0, sizeof(ccd));
|
||||
|
||||
/* create SDP session description */
|
||||
session = osmo_cc_new_session(conf, priv, NULL, NULL, NULL, 0, 0, NULL, NULL, 0);
|
||||
|
||||
/* check every line of SDP and parse its data */
|
||||
while(*sdp) {
|
||||
if ((p = strchr(sdp, '\r'))) {
|
||||
*p++ = '\0';
|
||||
if (*p == '\n')
|
||||
p++;
|
||||
line = sdp;
|
||||
sdp = p;
|
||||
} else if ((p = strchr(sdp, '\n'))) {
|
||||
*p++ = '\0';
|
||||
line = sdp;
|
||||
sdp = p;
|
||||
} else {
|
||||
line = sdp;
|
||||
sdp = strchr(sdp, '\0');
|
||||
}
|
||||
next_word = line + 2;
|
||||
line_no++;
|
||||
|
||||
if (line[0] == '\0')
|
||||
continue;
|
||||
|
||||
if (line[1] != '=') {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "SDP line %d = '%s' is garbage, expecting '=' as second character.\n", line_no, line);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(line[0]) {
|
||||
case 'v':
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> Version: %s\n", next_word);
|
||||
if (atoi(next_word) != 0) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "SDP line %d = '%s' describes unsupported version.\n", line_no, line);
|
||||
osmo_cc_free_session(session);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case 'o':
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> Originator: %s\n", next_word);
|
||||
/* Originator */
|
||||
word = wordsep(&next_word);
|
||||
if (!word)
|
||||
break;
|
||||
free((char *)session->origin_remote.username); // if already set
|
||||
session->origin_remote.username = strdup(word);
|
||||
word = wordsep(&next_word);
|
||||
if (!word)
|
||||
break;
|
||||
free((char *)session->origin_remote.sess_id); // if already set
|
||||
session->origin_remote.sess_id = strdup(word);
|
||||
word = wordsep(&next_word);
|
||||
if (!word)
|
||||
break;
|
||||
free((char *)session->origin_remote.sess_version); // if already set
|
||||
session->origin_remote.sess_version = strdup(word);
|
||||
word = wordsep(&next_word);
|
||||
if (!word)
|
||||
break;
|
||||
free((char *)session->origin_remote.nettype); // if already set
|
||||
session->origin_remote.nettype = strdup(word);
|
||||
word = wordsep(&next_word);
|
||||
if (!word)
|
||||
break;
|
||||
free((char *)session->origin_remote.addrtype); // if already set
|
||||
session->origin_remote.addrtype = strdup(word);
|
||||
word = wordsep(&next_word);
|
||||
if (!word)
|
||||
break;
|
||||
free((char *)session->origin_remote.unicast_address); // if already set
|
||||
session->origin_remote.unicast_address = strdup(word);
|
||||
break;
|
||||
case 's':
|
||||
/* Session Name */
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> Session Name: %s\n", next_word);
|
||||
free((char *)session->name); // if already set
|
||||
session->name = strdup(next_word);
|
||||
break;
|
||||
case 'c': /* Connection Data */
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> Connection Data: %s\n", next_word);
|
||||
if (media)
|
||||
cd = &media->connection_data_remote;
|
||||
else
|
||||
cd = &ccd;
|
||||
/* network type */
|
||||
if (!(word = wordsep(&next_word)))
|
||||
break;
|
||||
if (!strcmp(word, "IN"))
|
||||
cd->nettype = osmo_cc_session_nettype_inet;
|
||||
else {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Unsupported network type '%s' in SDP line %d = '%s'\n", word, line_no, line);
|
||||
break;
|
||||
}
|
||||
/* address type */
|
||||
if (!(word = wordsep(&next_word)))
|
||||
break;
|
||||
if (!strcmp(word, "IP4")) {
|
||||
cd->addrtype = osmo_cc_session_addrtype_ipv4;
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> Address Type = IPv4\n");
|
||||
} else
|
||||
if (!strcmp(word, "IP6")) {
|
||||
cd->addrtype = osmo_cc_session_addrtype_ipv6;
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> Address Type = IPv6\n");
|
||||
} else {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Unsupported address type '%s' in SDP line %d = '%s'\n", word, line_no, line);
|
||||
break;
|
||||
}
|
||||
/* connection address */
|
||||
if (!(word = wordsep(&next_word)))
|
||||
break;
|
||||
if ((p = strchr(word, '/')))
|
||||
*p++ = '\0';
|
||||
free((char *)cd->address); // in case of multiple lines of 'c'
|
||||
cd->address = strdup(word);
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> Address = %s\n", word);
|
||||
break;
|
||||
case 'm': /* Media Description */
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> Media Description: %s\n", next_word);
|
||||
/* add media description */
|
||||
media = osmo_cc_add_media(session, 0, 0, NULL, 0, 0, 0, csend, creceive, NULL, 0);
|
||||
/* copy common connection data from common connection, if exists */
|
||||
cd = &media->connection_data_remote;
|
||||
memcpy(cd, &ccd, sizeof(*cd));
|
||||
/* media type */
|
||||
if (!(word = wordsep(&next_word)))
|
||||
break;
|
||||
if (!strcmp(word, "audio"))
|
||||
media->description.type = osmo_cc_session_media_type_audio;
|
||||
else
|
||||
if (!strcmp(word, "video"))
|
||||
media->description.type = osmo_cc_session_media_type_video;
|
||||
else {
|
||||
media->description.type = osmo_cc_session_media_type_unknown;
|
||||
media->description.type_name = strdup(word);
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Unsupported media type in SDP line %d = '%s'\n", line_no, line);
|
||||
}
|
||||
/* port */
|
||||
if (!(word = wordsep(&next_word)))
|
||||
break;
|
||||
media->description.port_remote = atoi(word);
|
||||
/* proto */
|
||||
if (!(word = wordsep(&next_word)))
|
||||
break;
|
||||
if (!strcmp(word, "RTP/AVP"))
|
||||
media->description.proto = osmo_cc_session_media_proto_rtp;
|
||||
else {
|
||||
media->description.proto = osmo_cc_session_media_proto_unknown;
|
||||
media->description.proto_name = strdup(word);
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Unsupported protocol type in SDP line %d = '%s'\n", line_no, line);
|
||||
break;
|
||||
}
|
||||
/* create codec description for each codec and link */
|
||||
while ((word = wordsep(&next_word))) {
|
||||
/* create codec */
|
||||
codec = osmo_cc_add_codec(media, NULL, 0, 1, NULL, NULL, 0);
|
||||
/* fmt */
|
||||
codec->payload_type_remote = atoi(word);
|
||||
complete_codec_by_fmt(codec->payload_type_remote, &codec->payload_name, &codec->payload_rate, &codec->payload_channels);
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> payload type = %d\n", codec->payload_type_remote);
|
||||
if (codec->payload_name)
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> payload name = %s\n", codec->payload_name);
|
||||
if (codec->payload_rate)
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> payload rate = %d\n", codec->payload_rate);
|
||||
if (codec->payload_channels)
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> payload channels = %d\n", codec->payload_channels);
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> Attribute: %s\n", next_word);
|
||||
word = wordsep(&next_word);
|
||||
if (!strcmp(word, "sendrecv")) {
|
||||
if (media) {
|
||||
media->receive = 1;
|
||||
media->send = 1;
|
||||
} else {
|
||||
creceive = 1;
|
||||
csend = 1;
|
||||
}
|
||||
break;
|
||||
} else
|
||||
if (!strcmp(word, "recvonly")) {
|
||||
if (media) {
|
||||
media->receive = 1;
|
||||
media->send = 0;
|
||||
} else {
|
||||
creceive = 1;
|
||||
csend = 0;
|
||||
}
|
||||
break;
|
||||
} else
|
||||
if (!strcmp(word, "sendonly")) {
|
||||
if (media) {
|
||||
media->receive = 0;
|
||||
media->send = 1;
|
||||
} else {
|
||||
creceive = 0;
|
||||
csend = 1;
|
||||
}
|
||||
break;
|
||||
} else
|
||||
if (!strcmp(word, "inactive")) {
|
||||
if (media) {
|
||||
media->receive = 0;
|
||||
media->send = 0;
|
||||
} else {
|
||||
creceive = 0;
|
||||
csend = 0;
|
||||
}
|
||||
break;
|
||||
} else
|
||||
if (!media) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Attribute without previously defined media in SDP line %d = '%s'\n", line_no, line);
|
||||
break;
|
||||
}
|
||||
if (!strncmp(word, "rtpmap:", 7)) {
|
||||
int fmt = atoi(word + 7);
|
||||
osmo_cc_session_for_each_codec(media->codec_list, codec) {
|
||||
if (codec->payload_type_remote == fmt)
|
||||
break;
|
||||
}
|
||||
if (!codec) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Attribute without previously defined codec in SDP line %d = '%s'\n", line_no, line);
|
||||
break;
|
||||
}
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload type = %d\n", codec->payload_type_remote);
|
||||
if (!(word = wordsep(&next_word)))
|
||||
goto rtpmap_done;
|
||||
if ((p = strchr(word, '/')))
|
||||
*p++ = '\0';
|
||||
free((char *)codec->payload_name); // in case it is already set above
|
||||
codec->payload_name = strdup(word);
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload name = %s\n", codec->payload_name);
|
||||
if (!(word = p))
|
||||
goto rtpmap_done;
|
||||
if ((p = strchr(word, '/')))
|
||||
*p++ = '\0';
|
||||
codec->payload_rate = atoi(word);
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload rate = %d\n", codec->payload_rate);
|
||||
if (!(word = p)) {
|
||||
/* if no channel is given and no default was specified, we must set 1 channel */
|
||||
if (!codec->payload_channels)
|
||||
codec->payload_channels = 1;
|
||||
goto rtpmap_done;
|
||||
}
|
||||
codec->payload_channels = atoi(word);
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload channels = %d\n", codec->payload_channels);
|
||||
rtpmap_done:
|
||||
if (!codec->payload_name || !codec->payload_rate || !codec->payload_channels) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Broken 'rtpmap' definition in SDP line %d = '%s' Skipping codec!\n", line_no, line);
|
||||
osmo_cc_free_codec(codec);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if something is incomplete, abort here */
|
||||
if (osmo_cc_session_check(session, 1)) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Parsing SDP failed.\n");
|
||||
osmo_cc_free_session(session);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
void osmo_cc_debug_sdp(const char *_sdp)
|
||||
{
|
||||
const unsigned char *sdp = (const unsigned char *)_sdp;
|
||||
char text[256];
|
||||
int i;
|
||||
|
||||
while (*sdp) {
|
||||
for (i = 0; *sdp > 0 && *sdp >= 32 && i < (int)sizeof(text) - 1; i++)
|
||||
text[i] = *sdp++;
|
||||
text[i] = '\0';
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " | %s\n", text);
|
||||
while (*sdp > 0 && *sdp < 32)
|
||||
sdp++;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
char *osmo_cc_session_gensdp(struct osmo_cc_session *session);
|
||||
struct osmo_cc_session *osmo_cc_session_parsesdp(osmo_cc_session_config_t *conf, void *priv, const char *_sdp);
|
||||
int osmo_cc_payload_type_by_attrs(uint8_t *fmt, const char *name, uint32_t *rate, int *channels);
|
||||
void osmo_cc_debug_sdp(const char *sdp);
|
||||
|
|
@ -1,640 +0,0 @@
|
|||
/* Osmo-CC: Media Session 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 <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
#include <inttypes.h>
|
||||
#include "../libtimer/timer.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../liboptions/options.h"
|
||||
#include "endpoint.h"
|
||||
|
||||
#define NTP_OFFSET 2208988800
|
||||
|
||||
void osmo_cc_set_local_peer(osmo_cc_session_config_t *conf, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address)
|
||||
{
|
||||
conf->default_nettype = nettype;
|
||||
conf->default_addrtype = addrtype;
|
||||
conf->default_unicast_address = options_strdup(address);
|
||||
}
|
||||
|
||||
osmo_cc_session_t *osmo_cc_new_session(osmo_cc_session_config_t *conf, void *priv, const char *username, const char *sess_id, const char *sess_version, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *unicast_address, const char *session_name, int debug)
|
||||
{
|
||||
osmo_cc_session_t *session;
|
||||
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Creating session structure.\n");
|
||||
|
||||
session = calloc(1, sizeof(*session));
|
||||
if (!session) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "No mem!\n");
|
||||
abort();
|
||||
}
|
||||
session->config = conf;
|
||||
session->priv = priv;
|
||||
if (username) {
|
||||
int i;
|
||||
for (i = 0; username[i]; i++) {
|
||||
if ((uint8_t)username[i] < 33) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Fatal error: SDP's originator (username) uses invalid characters, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
session->origin_local.username = strdup(username);
|
||||
}
|
||||
if (!username)
|
||||
session->origin_local.username = strdup("-");
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> user name = %s\n", session->origin_local.username);
|
||||
if (sess_id)
|
||||
session->origin_local.sess_id = strdup(sess_id);
|
||||
if (sess_version)
|
||||
session->origin_local.sess_version = strdup(sess_version);
|
||||
if (!sess_id || !sess_version) {
|
||||
struct timeval tv;
|
||||
char ntp_timestamp[32];
|
||||
/* get time NTP format time stamp (time since 1900) */
|
||||
gettimeofday(&tv, NULL);
|
||||
sprintf(ntp_timestamp, "%" PRIu64, (uint64_t)tv.tv_sec + NTP_OFFSET);
|
||||
if (!sess_id)
|
||||
session->origin_local.sess_id = strdup(ntp_timestamp);
|
||||
if (!sess_version)
|
||||
session->origin_local.sess_version = strdup(ntp_timestamp);
|
||||
}
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> session ID = %s\n", session->origin_local.sess_id);
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> session version = %s\n", session->origin_local.sess_version);
|
||||
if (nettype)
|
||||
session->origin_local.nettype = strdup(osmo_cc_session_nettype2string(nettype));
|
||||
else
|
||||
session->origin_local.nettype = strdup(osmo_cc_session_nettype2string(conf->default_nettype));
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> network type = %s\n", session->origin_local.nettype);
|
||||
if (addrtype)
|
||||
session->origin_local.addrtype = strdup(osmo_cc_session_addrtype2string(addrtype));
|
||||
else
|
||||
session->origin_local.addrtype = strdup(osmo_cc_session_addrtype2string(conf->default_addrtype));
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> address type = %s\n", session->origin_local.addrtype);
|
||||
if (unicast_address)
|
||||
session->origin_local.unicast_address = strdup(unicast_address);
|
||||
else
|
||||
session->origin_local.unicast_address = strdup(conf->default_unicast_address);
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> unicast address = %s\n", session->origin_local.unicast_address);
|
||||
if (session_name)
|
||||
session->name = strdup(session_name);
|
||||
if (!session_name)
|
||||
session->name = strdup("-");
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> session name = %s\n", session->name);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
void osmo_cc_free_session(osmo_cc_session_t *session)
|
||||
{
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Free session structure.\n");
|
||||
|
||||
free((char *)session->origin_local.username);
|
||||
free((char *)session->origin_local.sess_id);
|
||||
free((char *)session->origin_local.sess_version);
|
||||
free((char *)session->origin_local.nettype);
|
||||
free((char *)session->origin_local.addrtype);
|
||||
free((char *)session->origin_local.unicast_address);
|
||||
free((char *)session->origin_remote.username);
|
||||
free((char *)session->origin_remote.sess_id);
|
||||
free((char *)session->origin_remote.sess_version);
|
||||
free((char *)session->origin_remote.nettype);
|
||||
free((char *)session->origin_remote.addrtype);
|
||||
free((char *)session->origin_remote.unicast_address);
|
||||
free((char *)session->name);
|
||||
while (session->media_list)
|
||||
osmo_cc_free_media(session->media_list);
|
||||
free(session);
|
||||
}
|
||||
|
||||
osmo_cc_session_media_t *osmo_cc_add_media(osmo_cc_session_t *session, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, enum osmo_cc_session_media_type type, uint16_t port, enum osmo_cc_session_media_proto proto, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), int debug)
|
||||
{
|
||||
osmo_cc_session_config_t *conf = session->config;
|
||||
osmo_cc_session_media_t *media, **mediap;
|
||||
|
||||
media = calloc(1, sizeof(*media));
|
||||
if (!media) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "No mem!\n");
|
||||
abort();
|
||||
}
|
||||
media->session = session;
|
||||
if (nettype)
|
||||
media->connection_data_local.nettype = nettype;
|
||||
else
|
||||
media->connection_data_local.nettype = conf->default_nettype;
|
||||
if (addrtype)
|
||||
media->connection_data_local.addrtype = addrtype;
|
||||
else
|
||||
media->connection_data_local.addrtype = conf->default_addrtype;
|
||||
if (address)
|
||||
media->connection_data_local.address = strdup(address);
|
||||
else
|
||||
media->connection_data_local.address = strdup(conf->default_unicast_address);
|
||||
media->description.type = type;
|
||||
media->description.port_local = port;
|
||||
media->description.proto = proto;
|
||||
media->send = send;
|
||||
media->receive = receive;
|
||||
media->receiver = receiver;
|
||||
media->tx_sequence = random();
|
||||
media->tx_timestamp = random();
|
||||
mediap = &media->session->media_list;
|
||||
while (*mediap)
|
||||
mediap = &((*mediap)->next);
|
||||
*mediap = media;
|
||||
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Adding session media.\n");
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> network type = %s\n", osmo_cc_session_nettype2string(media->connection_data_local.nettype));
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> address type = %s\n", osmo_cc_session_addrtype2string(media->connection_data_local.addrtype));
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> address = %s\n", media->connection_data_local.address);
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> media type = %s\n", osmo_cc_session_media_type2string(media->description.type));
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> media port = %d\n", media->description.port_local);
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> media proto = %s\n", osmo_cc_session_media_proto2string(media->description.proto));
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Opening and binding media port %d\n", media->description.port_local);
|
||||
|
||||
return media;
|
||||
}
|
||||
|
||||
void osmo_cc_free_media(osmo_cc_session_media_t *media)
|
||||
{
|
||||
osmo_cc_session_media_t **mediap;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Free session media.\n");
|
||||
|
||||
osmo_cc_rtp_close(media);
|
||||
free((char *)media->connection_data_local.nettype_name);
|
||||
free((char *)media->connection_data_local.addrtype_name);
|
||||
free((char *)media->connection_data_local.address);
|
||||
free((char *)media->connection_data_remote.nettype_name);
|
||||
free((char *)media->connection_data_remote.addrtype_name);
|
||||
free((char *)media->connection_data_remote.address);
|
||||
while (media->codec_list)
|
||||
osmo_cc_free_codec(media->codec_list);
|
||||
mediap = &media->session->media_list;
|
||||
while (*mediap != media)
|
||||
mediap = &((*mediap)->next);
|
||||
*mediap = media->next;
|
||||
free(media);
|
||||
}
|
||||
|
||||
osmo_cc_session_codec_t *osmo_cc_add_codec(osmo_cc_session_media_t *media, const char *payload_name, uint32_t payload_rate, int payload_channels, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), int debug)
|
||||
{
|
||||
osmo_cc_session_codec_t *codec, **codecp;
|
||||
int rc;
|
||||
|
||||
codec = calloc(1, sizeof(*codec));
|
||||
if (!codec) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "No mem!\n");
|
||||
abort();
|
||||
}
|
||||
codec->media = media;
|
||||
if (payload_name) {
|
||||
codec->payload_name = strdup(payload_name);
|
||||
codec->payload_rate = payload_rate;
|
||||
codec->payload_channels = payload_channels;
|
||||
rc = osmo_cc_payload_type_by_attrs(&codec->payload_type_local, payload_name, &payload_rate, &payload_channels);
|
||||
if (rc < 0) {
|
||||
/* hunt for next free dynamic payload type */
|
||||
uint8_t fmt = 96;
|
||||
osmo_cc_session_codec_t *c;
|
||||
osmo_cc_session_for_each_codec(media->codec_list, c) {
|
||||
if (c->payload_type_local >= fmt)
|
||||
fmt = c->payload_type_local + 1;
|
||||
}
|
||||
codec->payload_type_local = fmt;
|
||||
}
|
||||
}
|
||||
codec->encoder = encoder;
|
||||
codec->decoder = decoder;
|
||||
codecp = &codec->media->codec_list;
|
||||
while (*codecp)
|
||||
codecp = &((*codecp)->next);
|
||||
*codecp = codec;
|
||||
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Adding session codec.\n");
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload type = %d\n", codec->payload_type_local);
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload name = %s\n", codec->payload_name);
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload rate = %d\n", codec->payload_rate);
|
||||
if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload channels = %d\n", codec->payload_channels);
|
||||
|
||||
return codec;
|
||||
}
|
||||
|
||||
void osmo_cc_free_codec(osmo_cc_session_codec_t *codec)
|
||||
{
|
||||
osmo_cc_session_codec_t **codecp;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Free session codec.\n");
|
||||
|
||||
free((char *)codec->payload_name);
|
||||
codecp = &codec->media->codec_list;
|
||||
while (*codecp != codec)
|
||||
codecp = &((*codecp)->next);
|
||||
*codecp = codec->next;
|
||||
free(codec);
|
||||
}
|
||||
|
||||
int osmo_cc_session_check(osmo_cc_session_t *session, int remote)
|
||||
{
|
||||
struct osmo_cc_session_origin *orig;
|
||||
struct osmo_cc_session_media *media;
|
||||
struct osmo_cc_session_connection_data *cd;
|
||||
struct osmo_cc_session_media_description *md;
|
||||
struct osmo_cc_session_codec *codec;
|
||||
int i, j;
|
||||
|
||||
if (remote)
|
||||
orig = &session->origin_remote;
|
||||
else
|
||||
orig = &session->origin_local;
|
||||
if (!orig->username
|
||||
|| !orig->sess_id
|
||||
|| !orig->sess_version
|
||||
|| !orig->nettype
|
||||
|| !orig->addrtype
|
||||
|| !orig->unicast_address) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Missing data in session origin\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!session->name) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Missing data in session origin\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!session->media_list) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Missing media session\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
i = 0;
|
||||
osmo_cc_session_for_each_media(session->media_list, media) {
|
||||
i++;
|
||||
if (remote)
|
||||
cd = &media->connection_data_remote;
|
||||
else
|
||||
cd = &media->connection_data_local;
|
||||
if (!cd->nettype && !cd->nettype_name) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing connection network type\n", i);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!cd->addrtype && !cd->addrtype_name) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing connection address type\n", i);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!cd->address) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing connection address\n", i);
|
||||
return -EINVAL;
|
||||
}
|
||||
md = &media->description;
|
||||
if (!md->type && !md->type_name) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing media type\n", i);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!md->proto && !md->proto_name) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing protocol\n", i);
|
||||
return -EINVAL;
|
||||
}
|
||||
j = 0;
|
||||
osmo_cc_session_for_each_codec(media->codec_list, codec) {
|
||||
j++;
|
||||
if (!codec->payload_name) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d, codec #%d is missing name\n", i, j);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!codec->payload_rate) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d, codec #%d is missing rate\n", i, j);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!codec->payload_channels) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d, codec #%d is missing channel count\n", i, j);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* check session description and generate SDP */
|
||||
const char *osmo_cc_session_send_offer(osmo_cc_session_t *session)
|
||||
{
|
||||
const char *sdp;
|
||||
int rc;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Generating session offer and opening RTP stream.\n");
|
||||
|
||||
rc = osmo_cc_session_check(session, 0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
sdp = osmo_cc_session_gensdp(session);
|
||||
osmo_cc_debug_sdp(sdp);
|
||||
|
||||
return sdp;
|
||||
}
|
||||
|
||||
osmo_cc_session_t *osmo_cc_session_receive_offer(osmo_cc_session_config_t *conf, void *priv, const char *sdp)
|
||||
{
|
||||
osmo_cc_session_t *session;
|
||||
int rc;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Parsing session offer.\n");
|
||||
|
||||
osmo_cc_debug_sdp(sdp);
|
||||
session = osmo_cc_session_parsesdp(conf, priv, sdp);
|
||||
if (!session)
|
||||
return NULL;
|
||||
|
||||
rc = osmo_cc_session_check(session, 0);
|
||||
if (rc < 0) {
|
||||
osmo_cc_free_session(session);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
void osmo_cc_session_accept_media(osmo_cc_session_media_t *media, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len))
|
||||
{
|
||||
osmo_cc_session_config_t *conf = media->session->config;
|
||||
|
||||
media->accepted = 1;
|
||||
if (nettype)
|
||||
media->connection_data_local.nettype = nettype;
|
||||
else
|
||||
media->connection_data_local.nettype = conf->default_nettype;
|
||||
if (addrtype)
|
||||
media->connection_data_local.addrtype = addrtype;
|
||||
else
|
||||
media->connection_data_local.addrtype = conf->default_addrtype;
|
||||
free((char *)media->connection_data_local.address);
|
||||
if (address)
|
||||
media->connection_data_local.address = strdup(address);
|
||||
else
|
||||
media->connection_data_local.address = strdup(conf->default_unicast_address);
|
||||
media->send = send;
|
||||
media->receive = receive;
|
||||
media->receiver = receiver;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Accepting session media.\n");
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> network type = %s\n", osmo_cc_session_nettype2string(media->connection_data_local.nettype));
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> address type = %s\n", osmo_cc_session_addrtype2string(media->connection_data_local.addrtype));
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> address = %s\n", media->connection_data_local.address);
|
||||
}
|
||||
|
||||
|
||||
void osmo_cc_session_accept_codec(osmo_cc_session_codec_t *codec, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len))
|
||||
{
|
||||
codec->accepted = 1;
|
||||
codec->encoder = encoder;
|
||||
codec->decoder = decoder;
|
||||
/* when we accept a codec, we just use the same payload type as the remote */
|
||||
codec->payload_type_local = codec->payload_type_remote;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Accepting session codec.\n");
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> payload type = %d\n", codec->payload_type_local);
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> payload name = %s\n", codec->payload_name);
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> payload rate = %d\n", codec->payload_rate);
|
||||
PDEBUG(DCC, DEBUG_DEBUG, " -> payload channels = %d\n", codec->payload_channels);
|
||||
}
|
||||
|
||||
/* remove codecs/media that have not been accepted and generate SDP */
|
||||
const char *osmo_cc_session_send_answer(osmo_cc_session_t *session)
|
||||
{
|
||||
osmo_cc_session_media_t *media;
|
||||
osmo_cc_session_codec_t *codec, **codec_p;
|
||||
const char *sdp;
|
||||
int rc;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Generating session answer.\n");
|
||||
|
||||
/* loop all media */
|
||||
osmo_cc_session_for_each_media(session->media_list, media) {
|
||||
/* remove unaccepted codecs */
|
||||
codec_p = &media->codec_list;
|
||||
codec = *codec_p;
|
||||
while (codec) {
|
||||
if (!codec->accepted) {
|
||||
osmo_cc_free_codec(codec);
|
||||
codec = *codec_p;
|
||||
continue;
|
||||
}
|
||||
codec_p = &codec->next;
|
||||
codec = *codec_p;
|
||||
}
|
||||
/* mark media as unused, if no codec or not accepted */
|
||||
if (!media->accepted || !media->codec_list)
|
||||
media->description.port_local = 0;
|
||||
}
|
||||
|
||||
rc = osmo_cc_session_check(session, 0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
sdp = osmo_cc_session_gensdp(session);
|
||||
osmo_cc_debug_sdp(sdp);
|
||||
|
||||
return sdp;
|
||||
}
|
||||
|
||||
/* Apply remote session description to local session description.
|
||||
* If remote media's port is 0, remove from local session description.
|
||||
* If codecs in the remote session description are missing, remove from local session description.
|
||||
*/
|
||||
static int osmo_cc_session_negotiate(osmo_cc_session_t *session_local, struct osmo_cc_session *session_remote)
|
||||
{
|
||||
osmo_cc_session_media_t *media_local, *media_remote, **media_local_p;
|
||||
osmo_cc_session_codec_t *codec_local, *codec_remote, **codec_local_p;
|
||||
int rc;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Negotiating session.\n");
|
||||
|
||||
/* copy remote session information */
|
||||
session_local->origin_remote.username = strdup(session_remote->origin_remote.username);
|
||||
session_local->origin_remote.sess_id = strdup(session_remote->origin_remote.sess_id);
|
||||
session_local->origin_remote.sess_version = strdup(session_remote->origin_remote.sess_version);
|
||||
session_local->origin_remote.nettype = strdup(session_remote->origin_remote.nettype);
|
||||
session_local->origin_remote.addrtype = strdup(session_remote->origin_remote.addrtype);
|
||||
session_local->origin_remote.unicast_address = strdup(session_remote->origin_remote.unicast_address);
|
||||
|
||||
/* loop all media */
|
||||
for (media_local = session_local->media_list, media_remote = session_remote->media_list; media_local && media_remote; media_local = media_local->next, media_remote = media_remote->next) {
|
||||
/* copy remote media information */
|
||||
media_local->connection_data_remote.nettype = media_remote->connection_data_remote.nettype;
|
||||
if (media_remote->connection_data_remote.nettype_name)
|
||||
media_local->connection_data_remote.nettype_name = strdup(media_remote->connection_data_remote.nettype_name);
|
||||
media_local->connection_data_remote.addrtype = media_remote->connection_data_remote.addrtype;
|
||||
if (media_remote->connection_data_remote.addrtype_name)
|
||||
media_local->connection_data_remote.addrtype_name = strdup(media_remote->connection_data_remote.addrtype_name);
|
||||
if (media_remote->connection_data_remote.address)
|
||||
media_local->connection_data_remote.address = strdup(media_remote->connection_data_remote.address);
|
||||
media_local->description.port_remote = media_remote->description.port_remote;
|
||||
media_local->send = media_remote->send;
|
||||
media_local->receive = media_remote->receive;
|
||||
/* loop all codecs and remove if they are not found in local session description */
|
||||
codec_local_p = &media_local->codec_list;
|
||||
codec_local = *codec_local_p;
|
||||
while (codec_local) {
|
||||
/* search for equal codec, payload type may differe for each direction */
|
||||
osmo_cc_session_for_each_codec(media_remote->codec_list, codec_remote) {
|
||||
if (!strcmp(codec_local->payload_name, codec_remote->payload_name)
|
||||
&& codec_local->payload_rate == codec_remote->payload_rate
|
||||
&& codec_local->payload_channels == codec_remote->payload_channels)
|
||||
break;
|
||||
}
|
||||
if (!codec_remote) {
|
||||
osmo_cc_free_codec(codec_local);
|
||||
codec_local = *codec_local_p;
|
||||
continue;
|
||||
}
|
||||
/* copy remote codec information */
|
||||
codec_local->payload_type_remote = codec_remote->payload_type_remote;
|
||||
codec_local_p = &codec_local->next;
|
||||
codec_local = *codec_local_p;
|
||||
}
|
||||
}
|
||||
if (media_local) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Negotiation failed, because remote endpoint returns less media streams than we offered.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (media_remote) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Negotiation failed, because remote endpoint returns more media streams than we offered.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* remove media with port == 0 or no codec at all */
|
||||
media_local_p = &session_local->media_list;
|
||||
media_local = *media_local_p;
|
||||
while (media_local) {
|
||||
if (media_local->description.port_remote == 0 || !media_local->codec_list) {
|
||||
osmo_cc_free_media(media_local);
|
||||
media_local = *media_local_p;
|
||||
continue;
|
||||
}
|
||||
media_local_p = &media_local->next;
|
||||
media_local = *media_local_p;
|
||||
}
|
||||
|
||||
rc = osmo_cc_session_check(session_local, 1);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int osmo_cc_session_receive_answer(osmo_cc_session_t *session, const char *sdp)
|
||||
{
|
||||
osmo_cc_session_t *session_remote;
|
||||
int rc;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Parsing session answer.\n");
|
||||
|
||||
osmo_cc_debug_sdp(sdp);
|
||||
session_remote = osmo_cc_session_parsesdp(session->config, NULL, sdp);
|
||||
if (!session_remote)
|
||||
return -EINVAL;
|
||||
|
||||
rc = osmo_cc_session_check(session_remote, 1);
|
||||
if (rc < 0) {
|
||||
osmo_cc_free_session(session_remote);
|
||||
return rc;
|
||||
}
|
||||
rc = osmo_cc_session_negotiate(session, session_remote);
|
||||
if (rc < 0) {
|
||||
osmo_cc_free_session(session_remote);
|
||||
return rc;
|
||||
}
|
||||
osmo_cc_free_session(session_remote);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *osmo_cc_session_nettype2string(enum osmo_cc_session_nettype nettype)
|
||||
{
|
||||
switch (nettype) {
|
||||
case osmo_cc_session_nettype_inet:
|
||||
return "IN";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char *osmo_cc_session_addrtype2string(enum osmo_cc_session_addrtype addrtype)
|
||||
{
|
||||
switch (addrtype) {
|
||||
case osmo_cc_session_addrtype_ipv4:
|
||||
return "IP4";
|
||||
case osmo_cc_session_addrtype_ipv6:
|
||||
return "IP6";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char *osmo_cc_session_media_type2string(enum osmo_cc_session_media_type media_type)
|
||||
{
|
||||
switch (media_type) {
|
||||
case osmo_cc_session_media_type_audio:
|
||||
return "audio";
|
||||
case osmo_cc_session_media_type_video:
|
||||
return "video";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char *osmo_cc_session_media_proto2string(enum osmo_cc_session_media_proto media_proto)
|
||||
{
|
||||
switch (media_proto) {
|
||||
case osmo_cc_session_media_proto_rtp:
|
||||
return "RTP/AVP";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int osmo_cc_session_if_codec(osmo_cc_session_codec_t *codec, const char *name, uint32_t rate, int channels)
|
||||
{
|
||||
return (!strcmp(codec->payload_name, name)
|
||||
&& codec->payload_rate == rate
|
||||
&& codec->payload_channels == channels);
|
||||
}
|
||||
|
||||
int osmo_cc_session_handle(osmo_cc_session_t *session)
|
||||
{
|
||||
osmo_cc_session_media_t *media;
|
||||
int w = 0, rc;
|
||||
|
||||
osmo_cc_session_for_each_media(session->media_list, media) {
|
||||
do {
|
||||
rc = osmo_cc_rtp_receive(media);
|
||||
if (rc >= 0)
|
||||
w = 1;
|
||||
} while (rc >= 0);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
/* configuration */
|
||||
|
||||
enum osmo_cc_session_nettype {
|
||||
osmo_cc_session_nettype_unknown = 0,
|
||||
osmo_cc_session_nettype_inet,
|
||||
};
|
||||
|
||||
enum osmo_cc_session_addrtype {
|
||||
osmo_cc_session_addrtype_unknown = 0,
|
||||
osmo_cc_session_addrtype_ipv4,
|
||||
osmo_cc_session_addrtype_ipv6,
|
||||
};
|
||||
|
||||
typedef struct osmo_cc_session_config {
|
||||
enum osmo_cc_session_nettype default_nettype;
|
||||
enum osmo_cc_session_addrtype default_addrtype;
|
||||
const char *default_unicast_address;
|
||||
uint16_t rtp_port_next;
|
||||
uint16_t rtp_port_from;
|
||||
uint16_t rtp_port_to;
|
||||
} osmo_cc_session_config_t;
|
||||
|
||||
/* session description, global part: */
|
||||
|
||||
typedef struct osmo_cc_session_origin {
|
||||
const char *username;
|
||||
const char *sess_id;
|
||||
const char *sess_version;
|
||||
const char *nettype;
|
||||
const char *addrtype;
|
||||
const char *unicast_address;
|
||||
} osmo_cc_session_origin_t;
|
||||
|
||||
/* session instance */
|
||||
typedef struct osmo_cc_session {
|
||||
osmo_cc_session_config_t *config;
|
||||
void *priv;
|
||||
osmo_cc_session_origin_t origin_local, origin_remote;
|
||||
const char *name;
|
||||
struct osmo_cc_session_media *media_list;
|
||||
} osmo_cc_session_t;
|
||||
|
||||
/* connection description: */
|
||||
|
||||
typedef struct osmo_cc_session_connection_data {
|
||||
enum osmo_cc_session_nettype nettype;
|
||||
const char *nettype_name;
|
||||
enum osmo_cc_session_addrtype addrtype;
|
||||
const char *addrtype_name;
|
||||
const char *address;
|
||||
} osmo_cc_session_connection_data_t;
|
||||
|
||||
/* one media of session description: */
|
||||
|
||||
enum osmo_cc_session_media_type {
|
||||
osmo_cc_session_media_type_unknown,
|
||||
osmo_cc_session_media_type_audio,
|
||||
osmo_cc_session_media_type_video,
|
||||
};
|
||||
|
||||
enum osmo_cc_session_media_proto {
|
||||
osmo_cc_session_media_proto_unknown,
|
||||
osmo_cc_session_media_proto_rtp,
|
||||
};
|
||||
|
||||
typedef struct osmo_cc_session_media_description {
|
||||
enum osmo_cc_session_media_type type;
|
||||
const char *type_name;
|
||||
uint16_t port_local, port_remote;
|
||||
enum osmo_cc_session_media_proto proto;
|
||||
const char *proto_name;
|
||||
} osmo_cc_session_media_description_t;
|
||||
|
||||
/* media entry */
|
||||
typedef struct osmo_cc_session_media {
|
||||
struct osmo_cc_session_media *next;
|
||||
osmo_cc_session_t *session;
|
||||
osmo_cc_session_media_description_t description;
|
||||
osmo_cc_session_connection_data_t connection_data_local, connection_data_remote;
|
||||
struct osmo_cc_session_codec *codec_list;
|
||||
int send, receive;
|
||||
void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len);
|
||||
int rtp_socket;
|
||||
int rtcp_socket;
|
||||
uint32_t rtp_ssrc;
|
||||
uint16_t tx_sequence, rx_sequence;
|
||||
uint32_t tx_timestamp, rx_timestamp;
|
||||
int accepted;
|
||||
} osmo_cc_session_media_t;
|
||||
|
||||
/* codec entry */
|
||||
typedef struct osmo_cc_session_codec {
|
||||
struct osmo_cc_session_codec *next;
|
||||
osmo_cc_session_media_t *media;
|
||||
uint8_t payload_type_local, payload_type_remote; /* local = towards local, remote = toward remote */
|
||||
const char *payload_name;
|
||||
uint32_t payload_rate;
|
||||
int payload_channels;
|
||||
void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
|
||||
int accepted;
|
||||
} osmo_cc_session_codec_t;
|
||||
|
||||
#define osmo_cc_session_for_each_media(head, m) \
|
||||
for (m = (head); m; m = m->next)
|
||||
|
||||
#define osmo_cc_session_for_each_codec(head, c) \
|
||||
for (c = (head); c; c = c->next)
|
||||
|
||||
void osmo_cc_set_local_peer(osmo_cc_session_config_t *conf, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address);
|
||||
osmo_cc_session_t *osmo_cc_new_session(osmo_cc_session_config_t *conf, void *priv, const char *username, const char *sess_id, const char *sess_version, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *unicast_address, const char *session_name, int debug);
|
||||
void osmo_cc_free_session(osmo_cc_session_t *session);
|
||||
osmo_cc_session_media_t *osmo_cc_add_media(osmo_cc_session_t *session, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, enum osmo_cc_session_media_type type, uint16_t port, enum osmo_cc_session_media_proto proto, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), int debug);
|
||||
void osmo_cc_free_media(osmo_cc_session_media_t *media);
|
||||
osmo_cc_session_codec_t *osmo_cc_add_codec(osmo_cc_session_media_t *media, const char *playload_name, uint32_t playload_rate, int playload_channels, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), int debug);
|
||||
void osmo_cc_free_codec(osmo_cc_session_codec_t *codec);
|
||||
int osmo_cc_session_check(struct osmo_cc_session *session, int remote);
|
||||
const char *osmo_cc_session_send_offer(osmo_cc_session_t *session);
|
||||
osmo_cc_session_t *osmo_cc_session_receive_offer(osmo_cc_session_config_t *conf, void *priv, const char *sdp);
|
||||
void osmo_cc_session_accept_media(osmo_cc_session_media_t *media, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len));
|
||||
void osmo_cc_session_accept_codec(osmo_cc_session_codec_t *codec, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len));
|
||||
const char *osmo_cc_session_send_answer(osmo_cc_session_t *session);
|
||||
int osmo_cc_session_receive_answer(osmo_cc_session_t *session, const char *sdp);
|
||||
const char *osmo_cc_session_nettype2string(enum osmo_cc_session_nettype nettype);
|
||||
const char *osmo_cc_session_addrtype2string(enum osmo_cc_session_addrtype addrtype);
|
||||
const char *osmo_cc_session_media_type2string(enum osmo_cc_session_media_type media_type);
|
||||
const char *osmo_cc_session_media_proto2string(enum osmo_cc_session_media_proto media_proto);
|
||||
int osmo_cc_session_if_codec(osmo_cc_session_codec_t *codec, const char *name, uint32_t rate, int channels);
|
||||
int osmo_cc_session_handle(osmo_cc_session_t *session);
|
||||
|
|
@ -1,583 +0,0 @@
|
|||
/* Osmo-CC: 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 <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libtimer/timer.h"
|
||||
#include "message.h"
|
||||
#include "cause.h"
|
||||
#include "socket.h"
|
||||
|
||||
static const char version_string[] = OSMO_CC_VERSION;
|
||||
|
||||
static int _getaddrinfo(const char *host, uint16_t port, struct addrinfo **result)
|
||||
{
|
||||
char portstr[8];
|
||||
struct addrinfo hints;
|
||||
int rc;
|
||||
|
||||
sprintf(portstr, "%d", port);
|
||||
|
||||
/* bind socket */
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
hints.ai_protocol = 0;
|
||||
hints.ai_canonname = NULL;
|
||||
hints.ai_addr = NULL;
|
||||
hints.ai_next = NULL;
|
||||
|
||||
rc = getaddrinfo(host, portstr, &hints, result);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Failed to create socket for host '%s', port '%d': %s.\n", host, port, gai_strerror(rc));
|
||||
return rc;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* send a reject message toward CC process.
|
||||
* the CC process will change the reject message to a release message when not in INIT_IN state
|
||||
*/
|
||||
static void rej_msg(osmo_cc_socket_t *os, uint32_t callref, uint8_t socket_cause, uint8_t isdn_cause, uint16_t sip_cause)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
/* create message */
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_REQ);
|
||||
if (!msg)
|
||||
abort();
|
||||
|
||||
/* add cause */
|
||||
osmo_cc_add_ie_cause(msg, os->location, isdn_cause, sip_cause, socket_cause);
|
||||
osmo_cc_convert_cause_msg(msg);
|
||||
|
||||
/* message down */
|
||||
os->recv_msg_cb(os->priv, callref, msg);
|
||||
}
|
||||
|
||||
void tx_keepalive_timeout(struct timer *timer)
|
||||
{
|
||||
osmo_cc_conn_t *conn = (osmo_cc_conn_t *)timer->priv;
|
||||
osmo_cc_msg_t *msg;
|
||||
|
||||
/* send keepalive message */
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_DUMMY_REQ);
|
||||
osmo_cc_msg_list_enqueue(&conn->os->write_list, msg, conn->callref);
|
||||
timer_start(&conn->tx_keepalive_timer, OSMO_CC_SOCKET_TX_KEEPALIVE);
|
||||
}
|
||||
|
||||
static void close_conn(osmo_cc_conn_t *conn, uint8_t socket_cause);
|
||||
|
||||
void rx_keepalive_timeout(struct timer *timer)
|
||||
{
|
||||
osmo_cc_conn_t *conn = (osmo_cc_conn_t *)timer->priv;
|
||||
|
||||
PDEBUG(DCC, DEBUG_ERROR, "OsmoCC-Socket failed due to timeout.\n");
|
||||
close_conn(conn, OSMO_CC_SOCKET_CAUSE_TIMEOUT);
|
||||
}
|
||||
|
||||
/* create socket process and bind socket */
|
||||
int osmo_cc_open_socket(osmo_cc_socket_t *os, const char *host, uint16_t port, void *priv, void (*recv_msg_cb)(void *priv, uint32_t callref, osmo_cc_msg_t *msg), uint8_t location)
|
||||
{
|
||||
int try = 0, auto_port = 0;
|
||||
struct addrinfo *result, *rp;
|
||||
int rc, sock, flags;
|
||||
|
||||
memset(os, 0, sizeof(*os));
|
||||
|
||||
try_again:
|
||||
/* check for given port, if NULL, autoselect port */
|
||||
if (!port || auto_port) {
|
||||
port = OSMO_CC_DEFAULT_PORT + try;
|
||||
try++;
|
||||
auto_port = 1;
|
||||
}
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Create socket for host %s port %d.\n", host, port);
|
||||
|
||||
rc = _getaddrinfo(host, port, &result);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
for (rp = result; rp; rp = rp->ai_next) {
|
||||
int on = 1;
|
||||
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (sock < 0)
|
||||
continue;
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (unsigned char *)&on, sizeof(on));
|
||||
rc = bind(sock, rp->ai_addr, rp->ai_addrlen);
|
||||
if (rc == 0)
|
||||
break;
|
||||
close(sock);
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
if (rp == NULL) {
|
||||
if (auto_port && port < OSMO_CC_DEFAULT_PORT_MAX) {
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Failed to bind host %s port %d, trying again.\n", host, port);
|
||||
goto try_again;
|
||||
}
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Failed to bind given host %s port %d.\n", host, port);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* listen to socket */
|
||||
rc = listen(sock, 10);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Failed to listen on socket.\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* set nonblocking io */
|
||||
flags = fcntl(sock, F_GETFL);
|
||||
flags |= O_NONBLOCK;
|
||||
fcntl(sock, F_SETFL, flags);
|
||||
|
||||
os->socket = sock;
|
||||
os->recv_msg_cb = recv_msg_cb;
|
||||
os->priv = priv;
|
||||
os->location = location;
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
/* create a connection */
|
||||
static osmo_cc_conn_t *open_conn(osmo_cc_socket_t *os, int sock, uint32_t callref, int read_setup)
|
||||
{
|
||||
osmo_cc_conn_t *conn, **connp;
|
||||
|
||||
/* create connection */
|
||||
conn = calloc(1, sizeof(*conn));
|
||||
if (!conn) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "No mem!\n");
|
||||
abort();
|
||||
}
|
||||
conn->os = os;
|
||||
conn->socket = sock;
|
||||
conn->read_version = 1;
|
||||
conn->write_version = 1;
|
||||
conn->read_setup = read_setup;
|
||||
if (callref)
|
||||
conn->callref = callref;
|
||||
else
|
||||
conn->callref = osmo_cc_new_callref();
|
||||
|
||||
timer_init(&conn->tx_keepalive_timer, tx_keepalive_timeout, conn);
|
||||
timer_init(&conn->rx_keepalive_timer, rx_keepalive_timeout, conn);
|
||||
timer_start(&conn->tx_keepalive_timer, OSMO_CC_SOCKET_TX_KEEPALIVE);
|
||||
timer_start(&conn->rx_keepalive_timer, OSMO_CC_SOCKET_RX_KEEPALIVE);
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "New socket connection (callref %d).\n", conn->callref);
|
||||
|
||||
/* attach to list */
|
||||
connp = &os->conn_list;
|
||||
while (*connp)
|
||||
connp = &((*connp)->next);
|
||||
*connp = conn;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
/* remove a connection */
|
||||
static void close_conn(osmo_cc_conn_t *conn, uint8_t socket_cause)
|
||||
{
|
||||
osmo_cc_conn_t **connp;
|
||||
osmo_cc_msg_list_t *ml;
|
||||
|
||||
/* detach connection first, to prevent a destruction during message handling (double free) */
|
||||
connp = &conn->os->conn_list;
|
||||
while (*connp != conn)
|
||||
connp = &((*connp)->next);
|
||||
*connp = conn->next;
|
||||
/* send reject message, if socket_cause is set */
|
||||
if (socket_cause && !conn->read_setup) {
|
||||
/* receive a release or reject (depending on state), but only if we sent a setup */
|
||||
rej_msg(conn->os, conn->callref, socket_cause, 0, 0);
|
||||
}
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Destroy socket connection (callref %d).\n", conn->callref);
|
||||
|
||||
/* close socket */
|
||||
if (conn->socket)
|
||||
close(conn->socket);
|
||||
/* free partly received message */
|
||||
if (conn->read_msg)
|
||||
osmo_cc_free_msg(conn->read_msg);
|
||||
/* free send queue */
|
||||
while ((ml = conn->write_list)) {
|
||||
osmo_cc_free_msg(ml->msg);
|
||||
conn->write_list = ml->next;
|
||||
free(ml);
|
||||
}
|
||||
/* free timers */
|
||||
timer_exit(&conn->tx_keepalive_timer);
|
||||
timer_exit(&conn->rx_keepalive_timer);
|
||||
/* free connection (already detached above) */
|
||||
free(conn);
|
||||
}
|
||||
|
||||
/* close socket and remove */
|
||||
void osmo_cc_close_socket(osmo_cc_socket_t *os)
|
||||
{
|
||||
osmo_cc_msg_list_t *ml;
|
||||
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "Destroy socket.\n");
|
||||
|
||||
/* free all connections */
|
||||
while (os->conn_list)
|
||||
close_conn(os->conn_list, 0);
|
||||
/* close socket */
|
||||
if (os->socket > 0) {
|
||||
close(os->socket);
|
||||
os->socket = 0;
|
||||
}
|
||||
/* free send queue */
|
||||
while ((ml = os->write_list)) {
|
||||
osmo_cc_free_msg(ml->msg);
|
||||
os->write_list = ml->next;
|
||||
free(ml);
|
||||
}
|
||||
}
|
||||
|
||||
/* send message to send_queue of sock instance */
|
||||
int osmo_cc_sock_send_msg(osmo_cc_socket_t *os, uint32_t callref, osmo_cc_msg_t *msg, const char *host, uint16_t port)
|
||||
{
|
||||
osmo_cc_msg_list_t *ml;
|
||||
|
||||
/* turn _IND into _REQ and _CNF into _RSP */
|
||||
msg->type &= ~1;
|
||||
|
||||
/* create list entry */
|
||||
ml = osmo_cc_msg_list_enqueue(&os->write_list, msg, callref);
|
||||
if (host)
|
||||
strncpy(ml->host, host, sizeof(ml->host) - 1);
|
||||
ml->port = port;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* receive message
|
||||
* return 1 if work was done.
|
||||
*/
|
||||
static int receive_conn(osmo_cc_conn_t *conn)
|
||||
{
|
||||
uint8_t socket_cause = OSMO_CC_SOCKET_CAUSE_BROKEN_PIPE;
|
||||
int rc;
|
||||
osmo_cc_msg_t *msg;
|
||||
uint8_t msg_type;
|
||||
int len;
|
||||
int work = 0;
|
||||
|
||||
/* get version from remote */
|
||||
if (conn->read_version) {
|
||||
rc = recv(conn->socket, conn->read_version_string + conn->read_version_pos, strlen(version_string) - conn->read_version_pos, 0);
|
||||
if (rc < 0 && errno == EAGAIN)
|
||||
return work;
|
||||
work = 1;
|
||||
if (rc <= 0) {
|
||||
goto close;
|
||||
}
|
||||
conn->read_version_pos += rc;
|
||||
if (conn->read_version_pos == strlen(version_string)) {
|
||||
conn->read_version = 0;
|
||||
if (!!memcmp(conn->read_version_string, version_string, strlen(version_string) - 1)) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Remote does not seem to be an Osmo-CC socket, rejecting!\n");
|
||||
socket_cause = OSMO_CC_SOCKET_CAUSE_FAILED;
|
||||
goto close;
|
||||
}
|
||||
if (conn->read_version_string[strlen(version_string) - 1] != version_string[strlen(version_string) - 1]) {
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "Remote Osmo-CC socket has wrong version (local=%s, remote=%s), rejecting!\n", version_string, conn->read_version_string);
|
||||
socket_cause = OSMO_CC_SOCKET_CAUSE_VERSION_MISMATCH;
|
||||
goto close;
|
||||
}
|
||||
} else
|
||||
return work;
|
||||
}
|
||||
|
||||
try_next_message:
|
||||
/* read message header from remote */
|
||||
if (!conn->read_msg) {
|
||||
rc = recv(conn->socket, ((uint8_t *)&conn->read_hdr) + conn->read_pos, sizeof(conn->read_hdr) - conn->read_pos, 0);
|
||||
if (rc < 0 && errno == EAGAIN)
|
||||
return work;
|
||||
work = 1;
|
||||
if (rc <= 0) {
|
||||
goto close;
|
||||
}
|
||||
conn->read_pos += rc;
|
||||
if (conn->read_pos == sizeof(conn->read_hdr)) {
|
||||
conn->read_msg = osmo_cc_new_msg(conn->read_hdr.type);
|
||||
if (!conn->read_msg)
|
||||
abort();
|
||||
conn->read_msg->length_networkorder = conn->read_hdr.length_networkorder;
|
||||
/* prepare for reading message */
|
||||
conn->read_pos = 0;
|
||||
} else
|
||||
return work;
|
||||
}
|
||||
|
||||
/* read message data from remote */
|
||||
msg = conn->read_msg;
|
||||
len = ntohs(msg->length_networkorder);
|
||||
if (len == 0)
|
||||
goto empty_message;
|
||||
rc = recv(conn->socket, msg->data + conn->read_pos, len - conn->read_pos, 0);
|
||||
if (rc < 0 && errno == EAGAIN)
|
||||
return work;
|
||||
work = 1;
|
||||
if (rc <= 0) {
|
||||
goto close;
|
||||
}
|
||||
conn->read_pos += rc;
|
||||
if (conn->read_pos == len) {
|
||||
empty_message:
|
||||
/* start RX keepalive timeer, if not already */
|
||||
timer_start(&conn->rx_keepalive_timer, OSMO_CC_SOCKET_RX_KEEPALIVE);
|
||||
/* we got our setup message, so we clear the flag */
|
||||
conn->read_setup = 0;
|
||||
/* prepare for reading header */
|
||||
conn->read_pos = 0;
|
||||
/* detach message first, because the connection might be destroyed during message handling */
|
||||
msg_type = conn->read_msg->type;
|
||||
conn->read_msg = NULL;
|
||||
/* drop dummy or forward message */
|
||||
if (msg_type == OSMO_CC_MSG_DUMMY_REQ)
|
||||
osmo_cc_free_msg(msg);
|
||||
else
|
||||
conn->os->recv_msg_cb(conn->os->priv, conn->callref, msg);
|
||||
if (msg_type == OSMO_CC_MSG_REL_REQ || msg_type == OSMO_CC_MSG_REJ_REQ) {
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "closing socket because we received a release or reject message.\n");
|
||||
close_conn(conn, 0);
|
||||
return 1; /* conn removed */
|
||||
}
|
||||
goto try_next_message;
|
||||
}
|
||||
return work;
|
||||
|
||||
close:
|
||||
PDEBUG(DCC, DEBUG_ERROR, "OsmoCC-Socket failed, socket cause %d.\n", socket_cause);
|
||||
close_conn(conn, socket_cause);
|
||||
return work; /* conn removed */
|
||||
}
|
||||
|
||||
/* transmit message
|
||||
* return 1 if work was done.
|
||||
*/
|
||||
static int transmit_conn(osmo_cc_conn_t *conn)
|
||||
{
|
||||
uint8_t socket_cause = OSMO_CC_SOCKET_CAUSE_BROKEN_PIPE;
|
||||
int rc;
|
||||
osmo_cc_msg_t *msg;
|
||||
int len;
|
||||
osmo_cc_msg_list_t *ml;
|
||||
int work = 0;
|
||||
|
||||
/* send socket version to remote */
|
||||
if (conn->write_version) {
|
||||
rc = write(conn->socket, version_string, strlen(version_string));
|
||||
if (rc < 0 && errno == EAGAIN)
|
||||
return work;
|
||||
work = 1;
|
||||
if (rc <= 0) {
|
||||
goto close;
|
||||
}
|
||||
if (rc != strlen(version_string)) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "short write, please fix handling!\n");
|
||||
abort();
|
||||
}
|
||||
conn->write_version = 0;
|
||||
}
|
||||
|
||||
/* send message to remote */
|
||||
while (conn->write_list) {
|
||||
timer_stop(&conn->tx_keepalive_timer);
|
||||
msg = conn->write_list->msg;
|
||||
len = sizeof(*msg) + ntohs(msg->length_networkorder);
|
||||
rc = write(conn->socket, msg, len);
|
||||
if (rc < 0 && errno == EAGAIN)
|
||||
return work;
|
||||
work = 1;
|
||||
if (rc <= 0) {
|
||||
goto close;
|
||||
}
|
||||
if (rc != len) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "short write, please fix handling!\n");
|
||||
abort();
|
||||
}
|
||||
/* close socket after sending release/reject message */
|
||||
if (msg->type == OSMO_CC_MSG_REL_REQ || msg->type == OSMO_CC_MSG_REJ_REQ) {
|
||||
PDEBUG(DCC, DEBUG_DEBUG, "closing socket because we sent a release or reject message.\n");
|
||||
close_conn(conn, 0);
|
||||
return work; /* conn removed */
|
||||
}
|
||||
/* free message after sending */
|
||||
ml = conn->write_list;
|
||||
conn->write_list = ml->next;
|
||||
osmo_cc_free_msg(msg);
|
||||
free(ml);
|
||||
}
|
||||
|
||||
/* start TX keepalive timeer, if not already
|
||||
* because we stop at every message above, we actually restart the timer here.
|
||||
* only if there is no message for the amount of time, the timer fires.
|
||||
*/
|
||||
if (!timer_running(&conn->tx_keepalive_timer))
|
||||
timer_start(&conn->tx_keepalive_timer, OSMO_CC_SOCKET_TX_KEEPALIVE);
|
||||
|
||||
return work;
|
||||
|
||||
close:
|
||||
PDEBUG(DCC, DEBUG_NOTICE, "OsmoCC-Socket failed.\n");
|
||||
close_conn(conn, socket_cause);
|
||||
return work; /* conn removed */
|
||||
}
|
||||
|
||||
/* handle all sockets of a socket interface
|
||||
* return 1 if work was done.
|
||||
*/
|
||||
int osmo_cc_handle_socket(osmo_cc_socket_t *os)
|
||||
{
|
||||
struct sockaddr_storage sa;
|
||||
socklen_t slen = sizeof(sa);
|
||||
int sock;
|
||||
osmo_cc_conn_t *conn;
|
||||
osmo_cc_msg_list_t *ml, **mlp;
|
||||
int flags;
|
||||
struct addrinfo *result, *rp;
|
||||
int rc;
|
||||
int work = 0;
|
||||
|
||||
/* handle messages in send queue */
|
||||
while ((ml = os->write_list)) {
|
||||
work = 1;
|
||||
/* detach list entry */
|
||||
os->write_list = ml->next;
|
||||
ml->next = NULL;
|
||||
/* search for socket connection */
|
||||
for (conn = os->conn_list; conn; conn=conn->next) {
|
||||
if (conn->callref == ml->callref)
|
||||
break;
|
||||
}
|
||||
if (conn) {
|
||||
/* attach to list */
|
||||
mlp = &conn->write_list;
|
||||
while (*mlp)
|
||||
mlp = &((*mlp)->next);
|
||||
*mlp = ml;
|
||||
/* done with message */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* reject and release are ignored */
|
||||
if (ml->msg->type == OSMO_CC_MSG_REJ_REQ
|
||||
|| ml->msg->type == OSMO_CC_MSG_REL_REQ) {
|
||||
/* drop message */
|
||||
osmo_cc_free_msg(ml->msg);
|
||||
free(ml);
|
||||
/* done with message */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* reject, if this is not a setup message */
|
||||
if (ml->msg->type != OSMO_CC_MSG_SETUP_REQ
|
||||
&& ml->msg->type != OSMO_CC_MSG_ATTACH_REQ) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Message with unknown callref.\n");
|
||||
rej_msg(os, ml->callref, 0, OSMO_CC_ISDN_CAUSE_INVAL_CALLREF, 0);
|
||||
/* drop message */
|
||||
osmo_cc_free_msg(ml->msg);
|
||||
free(ml);
|
||||
/* done with message */
|
||||
continue;
|
||||
}
|
||||
/* connect to remote */
|
||||
rc = _getaddrinfo(ml->host, ml->port, &result);
|
||||
if (rc < 0) {
|
||||
rej_msg(os, ml->callref, OSMO_CC_SOCKET_CAUSE_FAILED, 0, 0);
|
||||
/* drop message */
|
||||
osmo_cc_free_msg(ml->msg);
|
||||
free(ml);
|
||||
/* done with message */
|
||||
continue;
|
||||
}
|
||||
for (rp = result; rp; rp = rp->ai_next) {
|
||||
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (sock < 0)
|
||||
continue;
|
||||
/* set nonblocking io */
|
||||
flags = fcntl(sock, F_GETFL);
|
||||
flags |= O_NONBLOCK;
|
||||
fcntl(sock, F_SETFL, flags);
|
||||
/* connect */
|
||||
rc = connect(sock, rp->ai_addr, rp->ai_addrlen);
|
||||
if (rc == 0 || errno == EINPROGRESS)
|
||||
break;
|
||||
close(sock);
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
if (rp == NULL) {
|
||||
PDEBUG(DCC, DEBUG_ERROR, "Failed to connect to given host %s port %d.\n", ml->host, ml->port);
|
||||
rej_msg(os, ml->callref, OSMO_CC_SOCKET_CAUSE_FAILED, 0, 0);
|
||||
/* drop message */
|
||||
osmo_cc_free_msg(ml->msg);
|
||||
free(ml);
|
||||
/* done with message */
|
||||
continue;
|
||||
}
|
||||
/* create connection */
|
||||
conn = open_conn(os, sock, ml->callref, 0);
|
||||
/* attach to list */
|
||||
conn->write_list = ml;
|
||||
/* done with (setup) message */
|
||||
}
|
||||
|
||||
/* handle new socket connection */
|
||||
while ((sock = accept(os->socket, (struct sockaddr *)&sa, &slen)) > 0) {
|
||||
work = 1;
|
||||
/* set nonblocking io */
|
||||
flags = fcntl(sock, F_GETFL);
|
||||
flags |= O_NONBLOCK;
|
||||
fcntl(sock, F_SETFL, flags);
|
||||
/* create connection */
|
||||
open_conn(os, sock, 0, 1);
|
||||
}
|
||||
|
||||
/* start with list after each read/write, because while handling (the message), one or more connections may be destroyed */
|
||||
for (conn = os->conn_list; conn; conn=conn->next) {
|
||||
/* check for rx */
|
||||
work = receive_conn(conn);
|
||||
/* if "change" is set, connection list might have changed, so we restart processing the list */
|
||||
if (work)
|
||||
break;
|
||||
/* check for tx */
|
||||
work = transmit_conn(conn);
|
||||
/* if "change" is set, connection list might have changed, so we restart processing the list */
|
||||
if (work)
|
||||
break;
|
||||
}
|
||||
|
||||
return work;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue