Imported libs

This commit is contained in:
Andreas Eversberg 2022-10-02 08:16:59 +02:00
parent 8f920659d5
commit 8c66ccbd71
56 changed files with 11401 additions and 0 deletions

7
src/libdebug/Makefile.am Normal file
View File

@ -0,0 +1,7 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libdebug.a
libdebug_a_SOURCES = \
debug.c

330
src/libdebug/debug.c Normal file
View File

@ -0,0 +1,330 @@
/* 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" },
{ "golay", "\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" },
{ "uk0", "\033[1;34m" },
{ "ph", "\033[0;33m" },
{ "dcf77", "\033[1;34m" },
{ "jitter", "\033[0;36m" },
{ NULL, NULL }
};
int debuglevel = DEBUG_INFO;
int debug_date = 0;
uint64_t debug_mask[2] = { ~0, ~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 = 0; // make GCC happy
if (debuglevel > level)
return;
if (!(debug_mask[cat >> 6] & ((uint64_t)1 << (cat & 63))))
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: %s\033[0;39m", debug_cat[cat].color, file, line, debug_cat[cat].name, 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)
memset(debug_mask, 0, sizeof(debug_mask));
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[i >> 6] |= ((uint64_t)1 << (i & 63));
}
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;
}

86
src/libdebug/debug.h Normal file
View File

@ -0,0 +1,86 @@
#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 DGOLAY 15
#define DFUENF 16
#define DFRAME 17
#define DCALL 18
#define DCC 19
#define DDB 20
#define DTRANS 21
#define DDMS 22
#define DSMS 23
#define DSDR 24
#define DUHD 25
#define DSOAPY 26
#define DWAVE 27
#define DRADIO 28
#define DAM791X 29
#define DUART 30
#define DDEVICE 31
#define DDATENKLO 32
#define DZEIT 33
#define DSIM1 34
#define DSIM2 35
#define DSIMI 36
#define DSIM7 37
#define DMTP2 38
#define DMTP3 39
#define DMUP 40
#define DROUTER 41
#define DSTDERR 42
#define DSS5 43
#define DISDN 44
#define DMISDN 45
#define DDSS1 46
#define DSIP 47
#define DTEL 48
#define DUK0 49
#define DPH 50
#define DDCF77 51
#define DJITTER 52
//NOTE: increment mask array, if 127 is exceeded
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);

7
src/libdtmf/Makefile.am Normal file
View File

@ -0,0 +1,7 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libdtmf.a
libdtmf_a_SOURCES = \
dtmf_encode.c \
dtmf_decode.c

250
src/libdtmf/dtmf_decode.c Normal file
View File

@ -0,0 +1,250 @@
/* 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 "../libdebug/debug.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";
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_reset(dtmf_dec_t *dtmf)
{
dtmf->detected = 0;
dtmf->count = 0;
}
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;
}
}

36
src/libdtmf/dtmf_decode.h Normal file
View File

@ -0,0 +1,36 @@
#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_reset(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);

130
src/libdtmf/dtmf_encode.c Normal file
View File

@ -0,0 +1,130 @@
/* 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 */
void dtmf_encode_init(dtmf_enc_t *dtmf, int samplerate, double dBm_level)
{
int i;
memset(dtmf, 0, sizeof(*dtmf));
dtmf->samplerate = samplerate;
for (i = 0; i < 65536; i++) {
dtmf->sine_low[i] = sin((double)i / 65536.0 * 2.0 * PI) * PEAK_DTMF_LOW * dBm_level;
dtmf->sine_high[i] = sin((double)i / 65536.0 * 2.0 * PI) * PEAK_DTMF_HIGH * dBm_level;
}
}
/* set dtmf tone */
int dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone, double on_duration, double off_duration)
{
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 -1;
}
dtmf->tone = tone;
dtmf->pos = 0;
dtmf->on = (int)((double)dtmf->samplerate * on_duration);
dtmf->off = dtmf->on + (int)((double)dtmf->samplerate * off_duration);
dtmf->phaseshift65536[0] = 65536.0 / ((double)dtmf->samplerate / f1);
dtmf->phaseshift65536[1] = 65536.0 / ((double)dtmf->samplerate / f2);
dtmf->phase65536[0] = 0.0;
dtmf->phase65536[1] = 0.0;
return 0;
}
/* Generate audio stream from DTMF tone.
* Keep phase for next call of function.
* Stop, if tone has finished and return only the samples that were used.
*/
int dtmf_encode(dtmf_enc_t *dtmf, sample_t *samples, int length)
{
double *phaseshift, *phase;
sample_t *sine_low, *sine_high;
int count = 0;
int i;
/* if no tone */
if (!dtmf->tone)
return 0;
sine_low = dtmf->sine_low;
sine_high = dtmf->sine_high;
phaseshift = dtmf->phaseshift65536;
phase = dtmf->phase65536;
for (i = 0; i < length; i++) {
*samples++ = sine_low[(uint16_t)phase[0]]
+ sine_high[(uint16_t)phase[1]];
phase[0] += phaseshift[0];
if (phase[0] >= 65536.0)
phase[0] -= 65536.0;
phase[1] += phaseshift[1];
if (phase[1] >= 65536.0)
phase[1] -= 65536.0;
dtmf->pos++;
/* tone ends */
if (dtmf->pos == dtmf->on) {
phaseshift[0] = 0.0;
phaseshift[1] = 0.0;
phase[0] = 0.0;
phase[1] = 0.0;
}
/* pause ends */
if (dtmf->pos == dtmf->off) {
dtmf->tone = 0;
break;
}
}
count += i;
return count;
}

17
src/libdtmf/dtmf_encode.h Normal file
View File

@ -0,0 +1,17 @@
typedef struct dtmf_enc {
int samplerate; /* samplerate */
char tone; /* current tone to be played */
int on, off; /* samples to turn on and afterwards off */
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 */
sample_t sine_low[65536]; /* sine tables at individual levels */
sample_t sine_high[65536];
} dtmf_enc_t;
void dtmf_encode_init(dtmf_enc_t *dtmf, int samplerate, double dBm_level);
int dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone, double on_duration, double off_duration);
int dtmf_encode(dtmf_enc_t *dtmf, sample_t *samples, int length);

View File

@ -0,0 +1,8 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libfilter.a
libfilter_a_SOURCES = \
iir_filter.c \
fir_filter.c

197
src/libfilter/fir_filter.c Normal file
View File

@ -0,0 +1,197 @@
/* 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;
}

View File

@ -0,0 +1,21 @@
#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 */

204
src/libfilter/iir_filter.c Normal file
View File

@ -0,0 +1,204 @@
/* 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

View File

@ -0,0 +1,17 @@
#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 */

6
src/libfm/Makefile.am Normal file
View File

@ -0,0 +1,6 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libfm.a
libfm_a_SOURCES = \
fm.c

413
src/libfm/fm.c Normal file
View File

@ -0,0 +1,413 @@
/* 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;
}

44
src/libfm/fm.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef _LIB_FM_H
#define _LIB_FM_H
#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);
#endif /* _LIB_FM_H */

6
src/libfsk/Makefile.am Normal file
View File

@ -0,0 +1,6 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libfsk.a
libfsk_a_SOURCES = \
fsk.c

448
src/libfsk/fsk.c Normal file
View File

@ -0,0 +1,448 @@
/* 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
/* uncomment to see the modulated curve */
//#define DEBUG_MODULATOR
/* uncomment to see the shape of the filter */
//#define DEBUG_MODULATOR_SHAPE
/*
* 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)
{
double temp;
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, 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->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->bits65536_per_sample = (double)bitrate / (double)samplerate * 65536.0;
PDEBUG(DDSP, DEBUG_DEBUG, "Bitduration of %.4f bits per sample @ %d.\n", fsk->bits65536_per_sample / 65536.0, samplerate);
fsk->phaseshift65536[0] = f0 / (double)samplerate * 65536.0;
fsk->phaseshift65536[1] = f1 / (double)samplerate * 65536.0;
PDEBUG(DDSP, DEBUG_DEBUG, "F0 = %.0f Hz (phaseshift65536[0] = %.4f)\n", f0, fsk->phaseshift65536[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;
if (filter) {
PDEBUG(DDSP, DEBUG_ERROR, "Cannot use FFSK with filter.\n");
rc = -EINVAL;
goto error;
}
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;
} else {
fsk->cycles_per_bit65536[0] = f0 / bitrate * 65536.0;
fsk->cycles_per_bit65536[1] = f1 / bitrate * 65536.0;
}
PDEBUG(DDSP, DEBUG_DEBUG, "F0 = %.0f Hz (cycles_per_bit65536[0] = %.4f)\n", f0, fsk->cycles_per_bit65536[0]);
PDEBUG(DDSP, DEBUG_DEBUG, "F1 = %.0f Hz (cycles_per_bit65536[1] = %.4f)\n", f1, fsk->cycles_per_bit65536[1]);
/* if filter is enabled, use a cosine shaped curve to change the phase each sample */
if (filter) {
fsk->phase_tab_0_1 = calloc(65536 + 65536, sizeof(*fsk->sin_tab));
if (!fsk->phase_tab_0_1) {
fprintf(stderr, "No mem!\n");
rc = -ENOMEM;
goto error;
}
fsk->phase_tab_1_0 = fsk->phase_tab_0_1 + 65536;
for (i = 0; i < 65536; i++) {
temp = cos((double)i / 65536.0 * PI) / 2 + 0.5; /* half cosine going from 1 to 0 */
fsk->phase_tab_0_1[i] = temp * fsk->phaseshift65536[0] + (1.0 - temp) * fsk->phaseshift65536[1];
fsk->phase_tab_1_0[i] = temp * fsk->phaseshift65536[1] + (1.0 - temp) * fsk->phaseshift65536[0];
#ifdef DEBUG_MODULATOR_SHAPE
fsk->phase_tab_0_1[i] = 1.0 - temp;
fsk->phase_tab_1_0[i] = temp;
#endif
}
PDEBUG(DDSP, DEBUG_DEBUG, "Enable filter to smooth FSK transmission.\n");
fsk->filter = 1;
}
/* must reset, because bit states must be initialized */
fsk_mod_reset(fsk);
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;
}
if (fsk->phase_tab_0_1) {
free(fsk->phase_tab_0_1);
fsk->phase_tab_0_1 = NULL;
fsk->phase_tab_1_0 = 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_last_bit = fsk->tx_bit;
fsk->tx_bit = fsk->send_bit(fsk->inst);
#ifdef DEBUG_MODULATOR
printf("bit change from %d to %d\n", fsk->tx_last_bit, fsk->tx_bit);
#endif
if (fsk->tx_bit < 0) {
fsk_mod_reset(fsk);
goto done;
}
fsk->tx_bit &= 1;
/* 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;
#ifdef DEBUG_MODULATOR
printf("phase %.3f bitpos=%.6f\n", phase / 65536.0, fsk->tx_bitpos65536 / 65536.0);
#endif
}
if (!fsk->filter) {
/* change phase forward to the current bit position */
phase += fsk->tx_bitpos65536 / 65536.0 * fsk->cycles_per_bit65536[fsk->tx_bit];
if (phase >= 65536.0)
phase -= 65536.0;
}
}
/* modulate bit */
if (!fsk->filter || fsk->tx_last_bit < 0 || fsk->tx_last_bit == fsk->tx_bit) {
/* without filtering or when there is no bit change */
phaseshift = fsk->phaseshift65536[fsk->tx_bit];
while (count < length && fsk->tx_bitpos65536 < 65536.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| %d\n", debug_amplitude(fsk->sin_tab[(uint16_t)phase] / fsk->level), fsk->tx_bit);
#endif
#ifdef DEBUG_MODULATOR_SHAPE
printf("|%s| %d\n", debug_amplitude(fsk->tx_bit), fsk->tx_bit);
#endif
phase += phaseshift;
if (phase >= 65536.0)
phase -= 65536.0;
fsk->tx_bitpos65536 += fsk->bits65536_per_sample;
}
} else if (fsk->tx_bit > fsk->tx_last_bit) {
/* with cosine shape filter, going from phase of 0 to phase of 1 */
while (count < length && fsk->tx_bitpos65536 < 65536.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| 0->1\n", debug_amplitude(fsk->sin_tab[(uint16_t)phase] / fsk->level));
#endif
#ifdef DEBUG_MODULATOR_SHAPE
printf("|%s|0->1\n", debug_amplitude(fsk->phase_tab_0_1[(uint16_t)fsk->tx_bitpos65536]));
#endif
phase += fsk->phase_tab_0_1[(uint16_t)fsk->tx_bitpos65536];
if (phase >= 65536.0)
phase -= 65536.0;
fsk->tx_bitpos65536 += fsk->bits65536_per_sample;
}
} else {
/* with cosine shape filter, going from phase of 1 to phase of 0 */
while (count < length && fsk->tx_bitpos65536 < 65536.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|1->0\n", debug_amplitude(fsk->sin_tab[(uint16_t)phase] / fsk->level));
#endif
#ifdef DEBUG_MODULATOR_SHAPE
printf("|%s|1->0\n", debug_amplitude(fsk->phase_tab_1_0[(uint16_t)fsk->tx_bitpos65536]));
#endif
phase += fsk->phase_tab_1_0[(uint16_t)fsk->tx_bitpos65536];
if (phase >= 65536.0)
phase -= 65536.0;
fsk->tx_bitpos65536 += fsk->bits65536_per_sample;
}
}
if (fsk->tx_bitpos65536 >= 65536.0) {
fsk->tx_bitpos65536 -= 65536.0;
if (!fsk->filter) {
/* change phase back to the point when bit has changed */
phase -= fsk->tx_bitpos65536 / 65536.0 * fsk->cycles_per_bit65536[fsk->tx_bit];
if (phase < 0.0)
phase += 65536.0;
}
goto next_bit;
}
done:
fsk->tx_phase65536 = phase;
return count;
}
/* reset transmitter state, so we get a clean start */
void fsk_mod_reset(fsk_mod_t *fsk)
{
fsk->tx_phase65536 = 0.0;
fsk->tx_bitpos65536 = 0.0;
fsk->tx_bit = -1;
fsk->tx_last_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;
}
}

49
src/libfsk/fsk.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef _LIB_FSK_H
#define _LIB_FSK_H
#include "../libfm/fm.h"
typedef struct fsk_mod {
void *inst;
int (*send_bit)(void *inst);
double bits65536_per_sample; /* fraction of a bit per sample */
double *sin_tab; /* sine table with correct peak level */
double *phase_tab_0_1; /* cosine shaped phase table (bit 0 to 1) */
double *phase_tab_1_0; /* cosine shaped phase table (bit 1 to 0) */
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) */
int tx_last_bit; /* last transmitting bit (-1 if not set) */
double tx_bitpos65536; /* current transmit position in bit */
int filter; /* set, if filters are used */
} 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_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);
#endif /* _LIB_FSK_H */

7
src/libg711/Makefile.am Normal file
View File

@ -0,0 +1,7 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libg711.a
libg711_a_SOURCES = \
g711.c

537
src/libg711/g711.c Normal file
View File

@ -0,0 +1,537 @@
/*****************************************************************************\
** **
** 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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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, void __attribute__((unused)) *priv)
{
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;
}

17
src/libg711/g711.h Normal file
View File

@ -0,0 +1,17 @@
void g711_init(void);
void g711_encode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_encode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_decode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_decode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_encode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_encode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_decode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_decode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_transcode_alaw_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_transcode_alaw_flipped_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_transcode_alaw_to_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_transcode_ulaw_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_transcode_ulaw_flipped_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_transcode_ulaw_to_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
void g711_transcode_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);

View File

@ -0,0 +1,6 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libjitter.a
libjitter_a_SOURCES = \
jitter.c

424
src/libjitter/jitter.c Normal file
View File

@ -0,0 +1,424 @@
/* Jitter buffering functions
*
* (C) 2022 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/>.
*/
/* How does it work:
*
* Storing:
*
* Each saved frame is sorted into the list of packages by their sequence
* number.
*
* The first packet will be stored with a delay of minimum jitter window size.
*
* Packets with the same sequence are dropped.
*
* Early packts that exceed maximum jitter window size cause jitter
* window to shift into the future.
*
* Late packets cause jitter window to shift into the past (allowing more
* delay). Minimum jitter window size is added also, to prevent subsequent
* packets from beeing late too.
*
* If no sequence is provided (autosequence), the sequence number is generated
* by a counter. Also the timestamp is generated by counting the length of each
* frame.
*
* If ssrc changes, the buffer is reset.
*
*
* Playout:
*
* The caller of the playout function can request any length of samples from
* the packet list. The packt's time stamp and the jitter window time stamp
* indicate what portion of a packet is already provided to the caller.
* Complete packet, sent to the caller, are removed.
*
* Missing packets are interpolated by repeating last 20ms of audio (optional)
* or by inserting zeroes (sample size > 1 byte) or by inserting 0xff (sample
* size = 1). In case of repeating audio, the number of turns are limited until
* buffer is reset to silence, if no frames are received for a certain time.
*
* Optionally the constant delay will be measured continuously and lowered if
* greater than minimum window size. (adaptive jitter buffer size)
*
* Note that the delay is measured with time stamp of frame, no matter what
* the length is. Length is an extra delay, but not considered here.
*
*
* Unlocking:
*
* If the buffer is created or reset, the buffer is locked, so no packets are
* stored. When the playout routine is called, the buffer is unlocked. This
* prevents from filling the buffer before playout is performed, which would
* cause high delay.
*
*/
#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 "jitter.h"
#define INITIAL_DELAY_INTERVAL 0.5
#define REPEAT_DELAY_INTERVAL 3.0
#define EXTRA_BUFFER 0.020 // 20 ms
#define EXTRA_TIMEOUT 0.500 // maximum time to repeat extrapolation buffer
/* uncomment to enable heavy debugging */
//#define HEAVY_DEBUG
static int unnamed_count = 1;
/* create jitter buffer */
int jitter_create(jitter_t *jb, const char *name, double samplerate, int sample_size, double target_window_duration, double max_window_duration, uint32_t window_flags)
{
int rc = 0;
memset(jb, 0, sizeof(*jb));
jb->sample_duration = 1.0 / samplerate;
jb->sample_size = sample_size;
jb->target_window_size = (int)(samplerate * target_window_duration);
jb->max_window_size = (int)(samplerate * max_window_duration);
jb->window_flags = window_flags;
jb->extra_size = (int)(EXTRA_BUFFER * samplerate);
jb->extra_samples = calloc(sample_size, jb->extra_size);
if (!jb->extra_samples) {
PDEBUG(DJITTER, DEBUG_ERROR, "No memory for frame.\n");
rc = -ENOMEM;
goto error;
}
jb->extra_timeout_max = (int)ceil(EXTRA_TIMEOUT / EXTRA_BUFFER);
/* optionally give a string to be show with the debug */
if (name && *name)
snprintf(jb->name, sizeof(jb->name) - 1, "(%s) ", name);
else
snprintf(jb->name, sizeof(jb->name) - 1, "(unnamed %d) ", unnamed_count++);
jitter_reset(jb);
PDEBUG(DJITTER, DEBUG_INFO, "%sCreated jitter buffer. (samplerate=%.0f, target_window=%.0fms, max_window=%.0fms, flag:latency=%s flag:repeat=%s)\n", jb->name, samplerate, target_window_duration * 1000.0, max_window_duration * 1000.0, (window_flags & JITTER_FLAG_LATENCY) ? "true" : "false", (window_flags & JITTER_FLAG_REPEAT) ? "true" : "false");
error:
if (rc)
jitter_destroy(jb);
return rc;
}
static void clear_extra_buffer(jitter_t *jb)
{
if (jb->sample_size == 1)
memset(jb->extra_samples, 0xff, jb->sample_size * jb->extra_size);
else
memset(jb->extra_samples, 0, jb->sample_size * jb->extra_size);
}
/* reset jitter buffer */
void jitter_reset(jitter_t *jb)
{
jitter_frame_t *jf, *temp;
PDEBUG(DJITTER, DEBUG_INFO, "%sReset jitter buffer.\n", jb->name);
/* jitter buffer locked */
jb->unlocked = 0;
/* window becomes invalid */
jb->window_valid = 0;
/* remove all pending frames */
jf = jb->frame_list;
while(jf) {
temp = jf;
jf = jf->next;
free(temp);
}
jb->frame_list = NULL;
/* clear extrapolation buffer */
if (jb->extra_samples)
clear_extra_buffer(jb);
jb->extra_index = 0;
jb->extra_timeout_count = jb->extra_timeout_max; /* no data in buffer yet, so we set timeout condition */
/* delay measurement and reduction */
jb->delay_counter = 0.0;
jb->delay_interval = INITIAL_DELAY_INTERVAL;
jb->min_delay_value = -1;
}
void jitter_destroy(jitter_t *jb)
{
jitter_reset(jb);
PDEBUG(DJITTER, DEBUG_INFO, "%sDestroying jitter buffer.\n", jb->name);
if (jb->extra_samples) {
free(jb->extra_samples);
jb->extra_samples = NULL;
}
}
/* store audio in jitterbuffer
*
* stop if buffer is completely filled
*/
void jitter_save(jitter_t *jb, void *samples, int length, int has_sequence, uint16_t sequence, uint32_t timestamp, uint32_t ssrc)
{
jitter_frame_t *jf, **jfp;
int16_t offset_sequence;
int32_t offset_timestamp;
/* ignore frames until the buffer is unlocked by jitter_load() */
if (!jb->unlocked)
return;
/* omit frames with no data */
if (length < 1)
return;
/* generate sequence and timestamp automatically, if enabled */
if (!has_sequence) {
#ifdef DEBUG_JITTER
PDEBUG(DJITTER, DEBUG_DEBUG, "%sSave frame of %d samples (no seqence).\n", jb->name, length);
#endif
sequence = jb->next_sequence;
jb->next_sequence++;
timestamp = jb->next_timestamp;
jb->next_timestamp += length;
ssrc = jb->window_ssrc;
} else {
#ifdef HEAVY_DEBUG
PDEBUG(DJITTER, DEBUG_DEBUG, "%sSave frame of %d samples (seqence=%u timestamp=%u ssrc=0x%02x).\n", jb->name, length, sequence, timestamp, ssrc);
#endif
jb->next_sequence = sequence + 1;
jb->next_timestamp = timestamp + length;
}
/* first packet (with this ssrc) sets window size to target_window_size */
if (!jb->window_valid || jb->window_ssrc != ssrc) {
if (!jb->window_valid)
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Initial frame after init or reset.\n", jb->name);
else
PDEBUG(DJITTER, DEBUG_DEBUG, "%s SSRC changed.\n", jb->name);
// NOTE: Reset must be called before finding the frame location below, because there will be no frame in list anymore!
jitter_reset(jb);
jb->unlocked = 1;
/* when using dynamic jitter buffer, we use half of the target delay */
if ((jb->window_flags & JITTER_FLAG_LATENCY)) {
jb->window_timestamp = timestamp - (uint32_t)jb->target_window_size / 2;
} else {
jb->window_timestamp = timestamp - (uint32_t)jb->target_window_size;
}
jb->window_valid = 1;
jb->window_ssrc = ssrc;
}
/* find location where to put frame into the list, depending on sequence number */
jfp = &jb->frame_list;
while(*jfp) {
offset_sequence = (int16_t)(sequence - (*jfp)->sequence);
/* found double entry */
if (offset_sequence == 0) {
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Dropping double packet (sequence = %d)\n", jb->name, sequence);
return;
}
/* offset is negative, so we found the position to insert frame */
if (offset_sequence < 0)
break;
jfp = &((*jfp)->next);
}
offset_timestamp = timestamp - jb->window_timestamp;
#ifdef HEAVY_DEBUG
PDEBUG(DJITTER, DEBUG_DEBUG, "%sFrame has offset of %.0fms in jitter buffer.\n", jb->name, (double)offset_timestamp * jb->sample_duration * 1000.0);
#endif
/* measure delay */
if (jb->min_delay_value < 0 || offset_timestamp < jb->min_delay_value)
jb->min_delay_value = offset_timestamp;
/* if frame is too early (delay ceases), shift window to the future */
if (offset_timestamp > jb->max_window_size) {
if ((jb->window_flags & JITTER_FLAG_LATENCY)) {
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Frame too early: Shift jitter buffer to the future, to make the frame fit to the end. (offset_timestamp(%d) > max_window_size(%d))\n", jb->name, offset_timestamp, jb->max_window_size);
/* shift window so it fits to the end of window */
jb->window_timestamp = timestamp - jb->max_window_size;
} else {
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Frame too early: Shift jitter buffer to the future, to make the frame fit to the target delay. (offset_timestamp(%d) > max_window_size(%d))\n", jb->name, offset_timestamp, jb->max_window_size);
/* shift window so frame fits to the start of window + target delay */
jb->window_timestamp = timestamp - (uint32_t)(jb->target_window_size);
}
}
/* is frame is too late, shift window to the past. */
if (offset_timestamp < 0) {
if ((jb->window_flags & JITTER_FLAG_LATENCY)) {
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Frame too late: Shift jitter buffer to the past, and add target window size. (offset_timestamp(%d) < 0)\n", jb->name, offset_timestamp);
/* shift window so frame fits to the start of window + half of target delay */
jb->window_timestamp = timestamp - (uint32_t)(jb->target_window_size) / 2;
} else {
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Frame too late: Shift jitter buffer to the past, and add half target window size. (offset_timestamp(%d) < 0)\n", jb->name, offset_timestamp);
/* shift window so frame fits to the start of window + target delay */
jb->window_timestamp = timestamp - (uint32_t)(jb->target_window_size);
}
}
/* insert or append frame */
#ifdef HEAVY_DEBUG
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Store frame\n", jb->name);
#endif
jf = malloc(sizeof(*jf) + length * jb->sample_size);
if (!jf) {
PDEBUG(DJITTER, DEBUG_ERROR, "No memory for frame.\n");
return;
}
memset(jf, 0, sizeof(*jf)); // note: clear header only
jf->sequence = sequence;
jf->timestamp = timestamp;
memcpy(jf->samples, samples, length * jb->sample_size);
jf->length = length;
jf->next = *jfp;
*jfp = jf;
}
/* get audio from jitterbuffer
*/
void jitter_load(jitter_t *jb, void *samples, int length)
{
jitter_frame_t *jf;
int32_t count, count2, index;
#ifdef HEAVY_DEBUG
PDEBUG(DJITTER, DEBUG_DEBUG, "%sLoad chunk of %d samples.\n", jb->name, length);
#endif
/* now unlock jitter buffer */
jb->unlocked = 1;
/* reduce delay */
jb->delay_counter += jb->sample_duration * (double)length;
if (jb->delay_counter >= jb->delay_interval) {
if (jb->min_delay_value >= 0)
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Statistics: target_window_delay=%.0fms max_window_delay=%.0fms current min_delay=%.0fms\n", jb->name, (double)jb->target_window_size * jb->sample_duration * 1000.0, (double)jb->max_window_size * jb->sample_duration * 1000.0, (double)jb->min_delay_value * jb->sample_duration * 1000.0);
/* delay reduction, if maximum delay is greater than target jitter window size */
if ((jb->window_flags & JITTER_FLAG_LATENCY) && jb->min_delay_value > jb->target_window_size) {
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Reducing current minimum delay of %.0fms, because maximum delay is greater than target window size of %.0fms.\n", jb->name, (double)jb->min_delay_value * jb->sample_duration * 1000.0, (double)jb->target_window_size * jb->sample_duration * 1000.0);
/* only reduce delay to half of the target window size */
jb->window_timestamp += jb->min_delay_value - jb->target_window_size / 2;
}
jb->delay_counter -= jb->delay_interval;
jb->delay_interval = REPEAT_DELAY_INTERVAL;
jb->min_delay_value = -1;
}
/* process all frames until output buffer is loaded */
while (length) {
/* always get frame with the lowest sequence number (1st frame) */
jf = jb->frame_list;
if (jf) {
count = jf->timestamp - jb->window_timestamp;
if (count > length)
count = length;
} else
count = length;
/* if there is no frame or we have not reached frame's time stamp, extrapolate */
if (count > 0) {
#ifdef HEAVY_DEBUG
if (jf)
PDEBUG(DJITTER, DEBUG_DEBUG, "%s There is a frame ahead in buffer after %d samples. Interpolating gap.\n", jb->name, jf->timestamp - jb->window_timestamp);
else
PDEBUG(DJITTER, DEBUG_DEBUG, "%s There is no frame ahead in buffer. Interpolating gap.\n", jb->name);
#endif
/* extrapolate by playing the extrapolation buffer */
while (count) {
count2 = count;
if (count2 > jb->extra_size - jb->extra_index)
count2 = jb->extra_size - jb->extra_index;
memcpy(samples, (uint8_t *)jb->extra_samples + jb->extra_index * jb->sample_size, count2 * jb->sample_size);
jb->extra_index += count2;
if (jb->extra_index == jb->extra_size) {
jb->extra_index = 0;
if ((jb->window_flags & JITTER_FLAG_REPEAT) && jb->extra_timeout_count < jb->extra_timeout_max) {
jb->extra_timeout_count++;
if (jb->extra_timeout_count == jb->extra_timeout_max) {
#ifdef HEAVY_DEBUG
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Repeated jitter buffer enough, clearing to silence.\n", jb->name);
#endif
clear_extra_buffer(jb);
}
}
}
samples = (uint8_t *)samples + count2 * jb->sample_size;
length -= count2;
jb->window_timestamp += count2;
count -= count2;
}
if (length == 0)
return;
}
/* copy samples from frame (what is not in the past) */
index = jb->window_timestamp - jf->timestamp;
while (index < jf->length) {
/* use the lowest value of 'playout length' or 'remaining packet length' */
count = length;
if (jf->length - index < count)
count = jf->length - index;
/* if extrapolation is to be written, limit count to what we can store into buffer */
if ((jb->window_flags & JITTER_FLAG_REPEAT) && jb->extra_size - jb->extra_index < count)
count = jb->extra_size - jb->extra_index;
/* copy samples from packet to play out, increment sample pointer and decrement length */
#ifdef HEAVY_DEBUG
PDEBUG(DJITTER, DEBUG_DEBUG, "%s Copy data (offset=%u count=%u) from frame (sequence=%u timestamp=%u length=%u).\n", jb->name, index, count, jf->sequence, jf->timestamp, jf->length);
#endif
memcpy(samples, (uint8_t *)jf->samples + index * jb->sample_size, count * jb->sample_size);
samples = (uint8_t *)samples + count * jb->sample_size;
length -= count;
/* copy frame data to extrapolation buffer also, increment index */
if ((jb->window_flags & JITTER_FLAG_REPEAT)) {
memcpy((uint8_t *)jb->extra_samples + jb->extra_index * jb->sample_size, (uint8_t *)jf->samples + index * jb->sample_size, count * jb->sample_size);
jb->extra_index += count;
if (jb->extra_index == jb->extra_size)
jb->extra_index = 0;
jb->extra_timeout_count = 0; /* now we have new data, we reset timeout condition */
}
/* increment time stamp */
jb->window_timestamp += count;
index += count;
/* if there was enough to play out, we are done */
if (length == 0)
return;
}
/* free frame, because all samples are now in the past */
jb->frame_list = jf->next;
free(jf);
/* now go for next loop, in case there is still date to play out */
}
}

60
src/libjitter/jitter.h Normal file
View File

@ -0,0 +1,60 @@
#define JITTER_FLAG_NONE 0 // no flags at all
#define JITTER_FLAG_LATENCY (1 << 0) // keep latency close to target_window_duration
#define JITTER_FLAG_REPEAT (1 << 1) // repeat audio to extrapolate gaps
/* window settings for low latency audio and extrapolation of gaps */
#define JITTER_AUDIO 0.050, 1.000, JITTER_FLAG_LATENCY | JITTER_FLAG_REPEAT
/* window settings for analog data (fax/modem) or digial data (HDLC) */
#define JITTER_DATA 0.100, 0.200, JITTER_FLAG_NONE
typedef struct jitter_frame {
struct jitter_frame *next;
uint16_t sequence;
uint32_t timestamp;
int length;
uint8_t samples[0];
} jitter_frame_t;
typedef struct jitter {
char name[64];
/* sample properties */
int sample_size;
double sample_duration;
/* automatic sequence generation */
uint16_t next_sequence;
uint32_t next_timestamp;
/* window properties */
int unlocked;
uint32_t window_flags;
int target_window_size;
int max_window_size;
int window_valid;
uint32_t window_ssrc;
uint32_t window_timestamp;
/* reduction of delay */
double delay_interval;
double delay_counter;
int32_t min_delay_value;
/* extrapolation */
int extra_size;
int extra_index;
void *extra_samples;
int extra_timeout_max;
int extra_timeout_count;
/* list of frames */
jitter_frame_t *frame_list;
} jitter_t;
int jitter_create(jitter_t *jb, const char *name, double samplerate, int sample_size, double target_window_duration, double max_window_duration, uint32_t window_flags);
void jitter_reset(jitter_t *jb);
void jitter_destroy(jitter_t *jb);
void jitter_save(jitter_t *jb, void *samples, int length, int has_sequence, uint16_t sequence, uint32_t timestamp, uint32_t ssrc);
void jitter_load(jitter_t *jb, void *samples, int length);

View File

@ -0,0 +1,7 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = liboptions.a
liboptions_a_SOURCES = \
options.c

339
src/liboptions/options.c Normal file
View File

@ -0,0 +1,339 @@
/* 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;
}

8
src/liboptions/options.h Normal file
View File

@ -0,0 +1,8 @@
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);

15
src/libosmocc/Makefile.am Normal file
View File

@ -0,0 +1,15 @@
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

251
src/libosmocc/cause.c Normal file
View File

@ -0,0 +1,251 @@
/* 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, did some corrections */
/* 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:
case 485:
case 604:
return 1; //SWITCH_CAUSE_UNALLOCATED_NUMBER;
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 18; //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 65; //SWITCH_CAUSE_BERER_CAPABILITY_NOT_IMPLEMENTED;
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; (not specified)
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;
}

5
src/libosmocc/cause.h Normal file
View File

@ -0,0 +1,5 @@
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);

1584
src/libosmocc/endpoint.c Normal file

File diff suppressed because it is too large Load Diff

131
src/libosmocc/endpoint.h Normal file
View File

@ -0,0 +1,131 @@
#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 */

193
src/libosmocc/helper.c Normal file
View File

@ -0,0 +1,193 @@
/* 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, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, 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, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, 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);
*session_p = NULL;
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);
*session_p = NULL;
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;
}

13
src/libosmocc/helper.h Normal file
View File

@ -0,0 +1,13 @@
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 *priv);
void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
};
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, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, 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, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, 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);

1293
src/libosmocc/message.c Normal file

File diff suppressed because it is too large Load Diff

512
src/libosmocc/message.h Normal file
View File

@ -0,0 +1,512 @@
#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_MODIFY_REQ = 0x90,
OSMO_CC_MSG_MODIFY_IND = 0x91,
OSMO_CC_MSG_MODIFY_RSP = 0x92,
OSMO_CC_MSG_MODIFY_CNF = 0x93,
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 */

404
src/libosmocc/rtp.c Normal file
View File

@ -0,0 +1,404 @@
/* 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, uint32_t *ssrc_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);
*ssrc_p = ntohl(rtph->ssrc);
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 marker, 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 | (marker << 7);
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->tx_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, uint8_t marker, int inc_sequence, int inc_timestamp, void *priv)
{
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, priv);
else {
payload = data;
payload_len = len;
}
rtp_send(codec->media->rtp_socket, payload, payload_len, marker, codec->payload_type_remote, codec->media->tx_sequence, codec->media->tx_timestamp, codec->media->tx_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, void *priv)
{
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, &media->rx_ssrc);
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, priv);
else {
data = payload;
len = payload_len;
}
if (codec->media->receive)
codec->media->receiver(codec, marker, media->rx_sequence, media->rx_timestamp, media->rx_ssrc, 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;
}
}

8
src/libosmocc/rtp.h Normal file
View File

@ -0,0 +1,8 @@
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, uint8_t marker, int inc_sequence, int inc_timestamp, void *priv);
int osmo_cc_rtp_receive(osmo_cc_session_media_t *media, void *priv);
void osmo_cc_rtp_close(osmo_cc_session_media_t *media);

693
src/libosmocc/screen.c Normal file
View File

@ -0,0 +1,693 @@
/* 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");
printf("When screening an incoming caller ID or dialed number, the '@' can be appended\n");
printf("to the 'new caller ID', followed by a 'host:port', to route call to a special\n");
printf("Osmo-CC endpoint. This way it is possible to do simple routing.\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;
calling_in = 1;
} 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 {
star_used = 0;
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';
}
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 {
at_used = star_used = 0;
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) {
if (routing_p)
*routing_p = &list->to[i + 1];
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;
}
if (routing_p && *routing_p)
PDEBUG(DCC, DEBUG_INFO, " -> remote = %s\n", *routing_p);
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, routing_p);
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), "", routing_p);
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;
}

7
src/libosmocc/screen.h Normal file
View File

@ -0,0 +1,7 @@
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);

544
src/libosmocc/sdp.c Normal file
View File

@ -0,0 +1,544 @@
/* 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++;
}
}

6
src/libosmocc/sdp.h Normal file
View File

@ -0,0 +1,6 @@
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);

640
src/libosmocc/session.c Normal file
View File

@ -0,0 +1,640 @@
/* 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 2208988800U
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, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, 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 *priv), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv), 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, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, 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 *priv), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv))
{
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, void *codec_priv)
{
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, codec_priv);
if (rc >= 0)
w = 1;
} while (rc >= 0);
}
return w;
}

130
src/libosmocc/session.h Normal file
View File

@ -0,0 +1,130 @@
/* 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, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len);
int rtp_socket;
int rtcp_socket;
uint32_t tx_ssrc, rx_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 *priv);
void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv);
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, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, 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 *priv), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv), 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, uint8_t marker, uint16_t sequence_number, uint32_t timestamp, uint32_t ssrc, 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 *priv), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len, void *priv));
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, void *codec_priv);

583
src/libosmocc/socket.c Normal file
View File

@ -0,0 +1,583 @@
/* 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;
}

44
src/libosmocc/socket.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef OSMO_CC_SOCKET_H
#define OSMO_CC_SOCKET_H
#define OSMO_CC_DEFAULT_PORT 4200
#define OSMO_CC_DEFAULT_PORT_MAX 4219
#define OSMO_CC_SOCKET_TX_KEEPALIVE 10.0
#define OSMO_CC_SOCKET_RX_KEEPALIVE 20.0
struct osmo_cc_socket;
typedef struct osmo_cc_conn {
struct osmo_cc_conn *next;
struct osmo_cc_socket *os;
int socket;
uint32_t callref;
int read_setup;
int read_version;
char read_version_string[sizeof(OSMO_CC_VERSION)]; /* must include 0-termination */
int read_version_pos;
int write_version;
osmo_cc_msg_t read_hdr;
osmo_cc_msg_t *read_msg;
int read_pos;
osmo_cc_msg_list_t *write_list;
struct timer tx_keepalive_timer;
struct timer rx_keepalive_timer;
} osmo_cc_conn_t;
typedef struct osmo_cc_socket {
int socket;
osmo_cc_conn_t *conn_list;
osmo_cc_msg_list_t *write_list;
void (*recv_msg_cb)(void *priv, uint32_t callref, osmo_cc_msg_t *msg);
void *priv;
uint8_t location;
} osmo_cc_socket_t;
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);
void osmo_cc_close_socket(osmo_cc_socket_t *os);
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);
int osmo_cc_handle_socket(osmo_cc_socket_t *os);
#endif /* OSMO_CC_SOCKET_H */

View File

@ -0,0 +1,7 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libph_socket.a
libph_socket_a_SOURCES = \
ph_socket.c

View File

@ -0,0 +1,299 @@
/* PH-socket, a lightweight ISDN physical layer interface
*
* (C) 2022 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 <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <time.h>
#include "../libdebug/debug.h"
#include "ph_socket.h"
#define CHAN s->name
int ph_socket_init(ph_socket_t *s, void (*ph_socket_rx_msg)(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length), void *priv, const char *socket_name, int server)
{
int rc, flags;
memset(s, 0, sizeof(*s));
s->name = socket_name;
s->ph_socket_rx_msg = ph_socket_rx_msg;
s->priv = priv;
s->tx_list_tail = &s->tx_list;
memset(&s->sock_address, 0, sizeof(s->sock_address));
s->sock_address.sun_family = AF_UNIX;
strcpy(s->sock_address.sun_path+1, socket_name);
if (server) {
rc = socket(PF_UNIX, SOCK_STREAM, 0);
if (rc < 0) {
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Failed to create UNIX socket.\n");
return rc;
}
s->listen = rc;
rc = bind(s->listen, (struct sockaddr *)(&s->sock_address), sizeof(s->sock_address));
if (rc < 0) {
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Failed to bind UNIX socket with path '%s' (errno = %d (%s)).\n", s->name, errno, strerror(errno));
return rc;
}
rc = listen(s->listen, 1);
if (rc < 0) {
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Failed to listen to UNIX socket with path '%s' (errno = %d (%s)).\n", s->name, errno, strerror(errno));
return rc;
}
/* set nonblocking io */
flags = fcntl(s->listen, F_GETFL);
flags |= O_NONBLOCK;
fcntl(s->listen, F_SETFL, flags);
}
PDEBUG_CHAN(DPH, DEBUG_INFO, "Created PH-socket at '%s'.\n", s->name);
return 0;
}
static void close_connection(ph_socket_t *s)
{
struct socket_msg_list *ml;
uint8_t disable = PH_CTRL_BLOCK;
if (s->connect <= 0)
return;
PDEBUG_CHAN(DPH, DEBUG_INFO, "Connection from PH-socket closed.\n");
/* indicate loss of socket connection */
s->ph_socket_rx_msg(s, 0, (s->listen > 0) ? PH_PRIM_CTRL_REQ : PH_PRIM_CTRL_IND, &disable, 1);
close(s->connect);
s->connect = 0;
while ((ml = s->tx_list)) {
s->tx_list = ml->next;
free(ml);
}
s->tx_list_tail = &s->tx_list;
if (s->rx_msg) {
free(s->rx_msg);
s->rx_msg = NULL;
}
/* set timer, so that retry is delayed */
static struct timespec tv;
clock_gettime(CLOCK_REALTIME, &tv);
double now = (double)tv.tv_sec + (double)tv.tv_nsec / 1000000000.0;
s->connect_timer = now + SOCKET_RETRY_TIMER;
}
void ph_socket_exit(ph_socket_t *s)
{
PDEBUG_CHAN(DPH, DEBUG_INFO, "Destroyed PH-socket.\n");
close_connection(s);
if (s->listen > 0) {
close(s->listen);
s->listen = 0;
}
}
int ph_socket_work(ph_socket_t *s)
{
uint8_t enable = PH_CTRL_UNBLOCK;
int rc, flags;
int work = 0;
if (s->listen > 0) {
struct sockaddr_un sock_address;
socklen_t sock_len = sizeof(sock_address);
/* see if there is an incoming connection */
rc = accept(s->listen, (struct sockaddr *)&sock_address, &sock_len);
if (rc > 0) {
if (s->connect > 0) {
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Rejecting incoming connection, because we already have a client connected!\n");
close(rc);
} else {
PDEBUG_CHAN(DPH, DEBUG_INFO, "Connection from PH-socket client.\n");
s->connect = rc;
/* set nonblocking io */
flags = fcntl(s->connect, F_GETFL);
flags |= O_NONBLOCK;
fcntl(s->connect, F_SETFL, flags);
/* reset rx buffer */
s->rx_header_index = 0;
s->rx_data_index = 0;
/* indicate established socket connection */
s->ph_socket_rx_msg(s, 0, PH_PRIM_CTRL_REQ, &enable, 1);
}
work = 1;
}
} else {
/* open connection, if closed */
if (s->connect <= 0) {
static struct timespec tv;
clock_gettime(CLOCK_REALTIME, &tv);
double now = (double)tv.tv_sec + (double)tv.tv_nsec / 1000000000.0;
if (s->connect_timer < now) {
PDEBUG_CHAN(DPH, DEBUG_DEBUG, "Trying to connect to PH-socket server.\n");
rc = socket(PF_UNIX, SOCK_STREAM, 0);
if (rc < 0) {
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Failed to create UNIX socket.\n");
s->connect_timer = now + SOCKET_RETRY_TIMER;
return 0;
}
s->connect = rc;
/* connect */
rc = connect(s->connect, (struct sockaddr *)&s->sock_address, sizeof(s->sock_address));
if (rc < 0 && errno != EAGAIN) {
if (!s->connect_failed)
PDEBUG_CHAN(DPH, DEBUG_NOTICE, "Failed to connect UNIX socket, retrying...\n");
close(s->connect);
s->connect_failed = 1;
s->connect = 0;
s->connect_timer = now + SOCKET_RETRY_TIMER;
return 0;
}
s->connect_failed = 0;
PDEBUG_CHAN(DPH, DEBUG_INFO, "Connection to PH-socket server.\n");
/* set nonblocking io */
flags = fcntl(s->connect, F_GETFL);
flags |= O_NONBLOCK;
fcntl(s->connect, F_SETFL, flags);
/* reset rx buffer */
s->rx_header_index = 0;
s->rx_data_index = 0;
/* indicate established socket connection */
s->ph_socket_rx_msg(s, 0, PH_PRIM_CTRL_IND, &enable, 1);
}
}
}
/* handle connection, if any */
if (s->connect > 0) {
if (!s->rx_msg)
s->rx_msg = calloc(1, sizeof(*s->rx_msg));
rx_again:
if (s->rx_header_index < (int)sizeof(s->rx_msg->msg.header)) {
/* read header until complete */
rc = recv(s->connect, ((uint8_t *)&s->rx_msg->msg.header) + s->rx_header_index, sizeof(s->rx_msg->msg.header) - s->rx_header_index, 0);
if (rc > 0) {
s->rx_header_index += rc;
work = 1;
goto rx_again;
} else if (rc == 0 || errno != EAGAIN) {
close_connection(s);
work = 1;
}
} else if (s->rx_data_index < s->rx_msg->msg.header.length) {
/* read data until complete */
rc = recv(s->connect, s->rx_msg->msg.data + s->rx_data_index, s->rx_msg->msg.header.length - s->rx_data_index, 0);
if (rc > 0) {
s->rx_data_index += rc;
work = 1;
goto rx_again;
} else if (rc == 0 || errno != EAGAIN) {
close_connection(s);
work = 1;
}
} else {
/* process and free message */
if (s->rx_msg->msg.header.prim != PH_PRIM_DATA_REQ
&& s->rx_msg->msg.header.prim != PH_PRIM_DATA_IND
&& s->rx_msg->msg.header.prim != PH_PRIM_DATA_CNF) {
PDEBUG_CHAN(DPH, DEBUG_DEBUG, "message 0x%02x channel %d from socket\n", s->rx_msg->msg.header.prim, s->rx_msg->msg.header.channel);
if (s->rx_msg->msg.header.length)
PDEBUG_CHAN(DPH, DEBUG_DEBUG, " -> data:%s\n", debug_hex(s->rx_msg->msg.data, s->rx_msg->msg.header.length));
}
s->ph_socket_rx_msg(s, s->rx_msg->msg.header.channel, s->rx_msg->msg.header.prim, s->rx_msg->msg.data, s->rx_msg->msg.header.length);
free(s->rx_msg);
s->rx_msg = NULL;
/* reset rx buffer */
s->rx_header_index = 0;
s->rx_data_index = 0;
}
tx_again:
if (s->tx_list) {
/* some frame in tx list, so try sending it */
rc = send(s->connect, ((uint8_t *)&s->tx_list->msg.header), sizeof(s->tx_list->msg.header) + s->tx_list->msg.header.length, 0);
if (rc > 0) {
struct socket_msg_list *ml;
if (rc != (int)sizeof(s->tx_list->msg.header) + s->tx_list->msg.header.length) {
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Short write, please fix handling!\n");
}
/* remove list entry */
ml = s->tx_list;
s->tx_list = ml->next;
if (s->tx_list == NULL)
s->tx_list_tail = &s->tx_list;
free(ml);
work = 1;
goto tx_again;
} else if (rc == 0 || errno != EAGAIN) {
close_connection(s);
work = 1;
}
}
}
return work;
}
void ph_socket_tx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length)
{
struct socket_msg_list *tx_msg;
if (prim != PH_PRIM_DATA_REQ
&& prim != PH_PRIM_DATA_IND
&& prim != PH_PRIM_DATA_CNF) {
PDEBUG_CHAN(DPH, DEBUG_DEBUG, "message 0x%02x channel %d to socket\n", prim, channel);
if (length)
PDEBUG_CHAN(DPH, DEBUG_DEBUG, " -> data:%s\n", debug_hex(data, length));
}
if (length > (int)sizeof(tx_msg->msg.data)) {
PDEBUG_CHAN(DPH, DEBUG_NOTICE, "Frame from HDLC process too large for socket, dropping!\n");
return;
}
if (s->connect <= 0) {
PDEBUG_CHAN(DPH, DEBUG_NOTICE, "Dropping message for socket, socket is closed!\n");
return;
}
tx_msg = calloc(1, sizeof(*tx_msg));
tx_msg->msg.header.channel = channel;
tx_msg->msg.header.prim = prim;
if (length) {
tx_msg->msg.header.length = length;
memcpy(tx_msg->msg.data, data, length);
}
/* move message to list */
*s->tx_list_tail = tx_msg;
s->tx_list_tail = &tx_msg->next;
}

View File

@ -0,0 +1,110 @@
#include <sys/un.h>
/*
* Procedure:
*
* If socket connection is establised, a PH_PRIM_CTRL_REQ message with
* PH_CTRL_ENABLE information is received by the socket server user.
* If the socket connection is lost, a PH_PRIM_CTRL_REQ message with
* PH_CTRL_DISABLE information is received by the user.
*
* If socket connection is establised, a PH_PRIM_CTRL_IND message with
* PH_CTRL_ENABLE information is received by the socket client user.
* If the socket connection is lost, a PH_PRIM_CTRL_IND message with
* PH_CTRL_DISABLE information is received by the user.
*
* The socket server should enable or disable interface depending on
* the PH_CTRL_ENABLE / PH_CTRL_DISABLE information.
*
* The socket client user shall keep track of last PH_PRIM_ACT_IND /
* PH_PRIM_DEACT_IND message and treat a PH_PRIM_CTRL_IND message with
* PH_CTRL_DISABLE information as a deactivation of all channels that
* were activated. Also it shall reject every PH_RIM_ACT_REQ with a
* PH_PRIM_DACT_IND, if the socket is currently unavailable.
*
* PH_PRIM_CTRL_REQ and PH_PRIM_CTRL_IND messages with PH_CTRL_ENABLE
* and PH_CTRL_DISABLE informations are not assoicated with a channel
* number. The socket sender shall set it to 0, the receiver shall
* ignore it.
*
* A missing MODE in PH_PRIM_ACT_REQ is interepreted as default:
* HDLC on D-channel, TRANS on B-channel.
*
* Each packet on the socket shall have the follwoing header:
* uint8_t channel;
* uint8_t prim;
* uint16_t length;
*
* The length shall be in host's endian on UN sockets and in network
* endian on TCP sockets and not being transmitted on UDP sockets.
*
* 0 to 65535 bytes shall follow the header, depending on the length
* information field.
*/
/* all primitives */
#define PH_PRIM_DATA_REQ 0x00 /* any data sent to channel from upper layer */
#define PH_PRIM_DATA_IND 0x01 /* any data received from channel to upper layer */
#define PH_PRIM_DATA_CNF 0x02 /* confirm data sent to channel */
#define PH_PRIM_CTRL_REQ 0x04 /* implementation specific requests towards interface */
#define PH_PRIM_CTRL_IND 0x05 /* implementation specific indications from interface */
#define PH_PRIM_ACT_REQ 0x08 /* activation request of channel, mode is given as payload */
#define PH_PRIM_ACT_IND 0x09 /* activation indication that the channel is now active */
#define PH_PRIM_DACT_REQ 0x0c /* deactivation request of channel */
#define PH_PRIM_DACT_IND 0x0d /* deactivation indication that the channel is now inactive */
/* one byte sent activation request */
#define PH_MODE_TRANS 0x00 /* raw data is sent via B-channel */
#define PH_MODE_HDLC 0x01 /* HDLC transcoding is performed via B-channel */
/* one byte sent with control messages */
#define PH_CTRL_BLOCK 0x00 /* disable (block) interface, when socket is disconnected */
#define PH_CTRL_UNBLOCK 0x01 /* enable (unblock) interface, when socket is connected */
#define PH_CTRL_LOOP_DISABLE 0x04 /* disable loopback */
#define PH_CTRL_LOOP1_ENABLE 0x05 /* enable LT transceier loopback */
#define PH_CTRL_LOOP2_ENABLE 0x06 /* enable NT transceier loopback */
#define PH_CTRL_LOOP_ERROR 0x10 /* frame error report (loopback test) */
#define PH_CTRL_VIOLATION_LT 0x11 /* code violation received by LT */
#define PH_CTRL_VIOLATION_NT 0x12 /* code violation received by NT */
struct socket_msg {
struct {
uint8_t channel;
uint8_t prim;
uint16_t length;
} header;
uint8_t data[65536];
} __attribute__((packed));
struct socket_msg_list {
struct socket_msg_list *next;
struct socket_msg msg;
};
#define SOCKET_RETRY_TIMER 2.0
typedef struct ph_socket {
const char *name;
void (*ph_socket_rx_msg)(struct ph_socket *s, int channel, uint8_t prim, uint8_t *data, int length);
void *priv;
struct sockaddr_un sock_address;
int listen; /* socket to listen to incoming connections */
int connect; /* socket of incoming connection */
int connect_failed; /* used to print a failure only once */
double connect_timer; /* time when to connect again */
struct socket_msg_list *tx_list, **tx_list_tail;
struct socket_msg_list *rx_msg;
int rx_header_index;
int rx_data_index;
} ph_socket_t;
int ph_socket_init(ph_socket_t *s, void (*ph_socket_rx_msg)(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length), void *priv, const char *socket_name, int server);
void ph_socket_exit(ph_socket_t *s);
void ph_socket_tx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length);
int ph_socket_work(ph_socket_t *s);

View File

@ -0,0 +1,6 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libsample.a
libsample_a_SOURCES = \
sample.c

89
src/libsample/sample.c Normal file
View File

@ -0,0 +1,89 @@
/* Sample definition
*
* (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>
#include "sample.h"
/*
* A regular voice conversation takes place at this factor below the full range
* of 16 bits signed value:
*/
static double int_16_speech_level = SPEECH_LEVEL * 0.7079; /* 16 dBm below dBm0, which is about 3dBm below full 16 bit range */
static double int_16_1mw_level = 0.7079; /* dBm0, 3dBm below full 16 bit range */
/* A sample_t is a value that has virtually infinite precision but will also
* support high numbers. 'double' or 'float' types are sufficient.
*
* When using sample_t inside signal processing of each base station, the
* level of +- 1 is relative to the normal speech evenlope.
*
* When converting sample_t to int16_t, the level of +- 1 is reduced by factor.
* This way the speech may be louder before clipping happens.
*
* When using sample_t to modulate (SDR or sound card), the level is changed,
* so it represents the frequency deviation in Hz. The deviation of speech
* envelope is network dependent.
*/
/* sample conversion relative to SPEECH level */
void samples_to_int16_speech(int16_t *spl, sample_t *samples, int length)
{
int32_t value;
while (length--) {
value = *samples++ * int_16_speech_level * 32768.0;
if (value > 32767.0)
*spl++ = 32767;
else if (value < -32767.0)
*spl++ = -32767;
else
*spl++ = (uint16_t)value;
}
}
void int16_to_samples_speech(sample_t *samples, int16_t *spl, int length)
{
while (length--) {
*samples++ = (double)(*spl++) / 32767.0 / int_16_speech_level;
}
}
/* sample conversion relative to 1mW level */
void samples_to_int16_1mw(int16_t *spl, sample_t *samples, int length)
{
int32_t value;
while (length--) {
value = *samples++ * int_16_1mw_level * 32768.0;
if (value > 32767.0)
*spl++ = 32767;
else if (value < -32767.0)
*spl++ = -32767;
else
*spl++ = (uint16_t)value;
}
}
void int16_to_samples_1mw(sample_t *samples, int16_t *spl, int length)
{
while (length--) {
*samples++ = (double)(*spl++) / 32767.0 / int_16_1mw_level;
}
}

10
src/libsample/sample.h Normal file
View File

@ -0,0 +1,10 @@
typedef double sample_t;
#define SPEECH_LEVEL 0.1585
void samples_to_int16_speech(int16_t *spl, sample_t *samples, int length);
void int16_to_samples_speech(sample_t *samples, int16_t *spl, int length);
void samples_to_int16_1mw(int16_t *spl, sample_t *samples, int length);
void int16_to_samples_1mw(sample_t *samples, int16_t *spl, int length);

6
src/libtimer/Makefile.am Normal file
View File

@ -0,0 +1,6 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
noinst_LIBRARIES = libtimer.a
libtimer_a_SOURCES = \
timer.c

119
src/libtimer/timer.c Normal file
View File

@ -0,0 +1,119 @@
/* Timer handling
*
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
//#include <sys/time.h>
#include <time.h>
#include <errno.h>
#include "timer.h"
static struct timer *timer_head = NULL;
static struct timer **timer_tail_p = &timer_head;
double get_time(void)
{
static struct timespec tv;
clock_gettime(CLOCK_REALTIME, &tv);
return (double)tv.tv_sec + (double)tv.tv_nsec / 1000000000.0;
}
void timer_init(struct timer *timer, void (*fn)(struct timer *timer), void *priv)
{
if (timer->linked) {
fprintf(stderr, "Timer is already initialized, aborting!\n");
abort();
}
timer->timeout = 0;
timer->fn = fn;
timer->priv = priv;
timer->next = NULL;
*timer_tail_p = timer;
timer_tail_p = &timer->next;
timer->linked = 1;
}
void timer_exit(struct timer *timer)
{
timer_tail_p = &timer_head;
while (*timer_tail_p) {
if (timer == *timer_tail_p)
*timer_tail_p = (*timer_tail_p)->next;
else
timer_tail_p = &((*timer_tail_p)->next);
}
timer->linked = 0;
}
void timer_start(struct timer *timer, double duration)
{
if (!timer->linked) {
fprintf(stderr, "Timer is not initialized, aborting!\n");
abort();
}
timer->duration = duration;
timer->timeout = get_time() + duration;
}
void timer_stop(struct timer *timer)
{
if (!timer->linked) {
fprintf(stderr, "Timer is not initialized, aborting!\n");
abort();
}
timer->timeout = 0;
}
int timer_running(struct timer *timer)
{
if (!timer->linked) {
fprintf(stderr, "Timer is not initialized, aborting!\n");
abort();
}
return (timer->timeout != 0);
}
void process_timer(void)
{
struct timer *timer;
double now;
now = get_time();
again:
timer = timer_head;
while (timer) {
if (timer->linked && timer->timeout > 0 && now >= timer->timeout) {
timer->timeout = 0;
timer->fn(timer);
goto again;
}
timer = timer->next;
}
}

18
src/libtimer/timer.h Normal file
View File

@ -0,0 +1,18 @@
struct timer {
struct timer *next;
int linked; /* set is timer is initialized and linked */
double duration;
double timeout;
void (*fn)(struct timer *timer);
void *priv;
};
double get_time(void);
void timer_init(struct timer *timer, void (*fn)(struct timer *timer), void *priv);
void timer_exit(struct timer *timer);
void timer_start(struct timer *timer, double duration);
void timer_stop(struct timer *timer);
int timer_running(struct timer *timer);
void process_timer(void);