diff options
Diffstat (limited to 'src/ss5/dsp.c')
-rw-r--r-- | src/ss5/dsp.c | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/src/ss5/dsp.c b/src/ss5/dsp.c new file mode 100644 index 0000000..4a15937 --- /dev/null +++ b/src/ss5/dsp.c @@ -0,0 +1,552 @@ +/* DSP functions + * + * (C) 2020 by Andreas Eversberg <jolly@eversberg.eu> + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define CHAN ((ss5_t *)(dsp->priv))->name + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <math.h> +#include <errno.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include "../libdebug/debug.h" +#include "ss5.h" + +//#define DEBUG_DEMODULATOR + +#define NUM_TONES 8 +#define db2level(db) pow(10, (double)(db) / 20.0) +#define level2db(level) (20 * log10(level)) + +static double tone_dbm[NUM_TONES] = { -7, -7, -7, -7, -7, -7, -9, -9 }; +static double tone_freq[NUM_TONES] = { 700, 900, 1100, 1300, 1500, 1700, 2400, 2600 }; +static double tone_width[NUM_TONES] = { 25, 25, 25, 25, 25, 25, 25, 25 }; + +static double tone_min_dbm[NUM_TONES] = { -14, -14, -14, -14, -14, -14, -16, -16 }; +static double tone_min_ampl_sq[NUM_TONES]; +static double tone_diff_db[NUM_TONES] = { 4, 4, 4, 4, 4, 4, 5, 5 }; +static double tone_diff_ampl_sq[NUM_TONES]; + +#define INTERRUPT_DURATION 0.015 +#define SPLIT_DURATION 0.030 +#define MF_RECOGNITION 0.025 + +int dsp_init_inst(dsp_t *dsp, void *priv, double samplerate, double sense_db) +{ + double tone_amplitude[NUM_TONES]; + int t; + + PDEBUG(DDSP, DEBUG_DEBUG, "Init DSP for SS5 instance.\n"); + + memset(dsp, 0, sizeof(*dsp)); + dsp->priv = priv; + dsp->samplerate = samplerate; + dsp->interrupt_duration = (int)(1000.0 * INTERRUPT_DURATION); + dsp->split_duration = (int)(1000.0 * SPLIT_DURATION); + dsp->mf_detect_duration = (int)(1000.0 * MF_RECOGNITION); + dsp->ms_per_sample = 1000.0 / samplerate; + dsp->detect_tone = ' '; + + /* convert dbm of tones to speech level */ + for (t = 0; t < NUM_TONES; t++) { + tone_amplitude[t] = db2level(tone_dbm[t]) / SPEECH_LEVEL; + tone_min_ampl_sq[t] = pow(db2level(tone_min_dbm[t] - sense_db) / SPEECH_LEVEL, 2); + tone_diff_ampl_sq[t] = pow(db2level(tone_diff_db[t]), 2); + } + + /* init MF modulator */ + dsp->mf_mod = mf_mod_init(samplerate, NUM_TONES, tone_freq, tone_amplitude); + if (!dsp->mf_mod) + return -EINVAL; + + /* init MF demodulator */ + dsp->mf_demod = mf_demod_init(samplerate, NUM_TONES, tone_freq, tone_width); + if (!dsp->mf_mod) + return -EINVAL; + + return 0; +} + +void dsp_cleanup_inst(dsp_t *dsp) +{ + PDEBUG(DDSP, DEBUG_DEBUG, "Cleanup DSP of SS5 instance.\n"); + + if (dsp->mf_mod) { + mf_mod_exit(dsp->mf_mod); + dsp->mf_mod = NULL; + } + + if (dsp->mf_demod) { + mf_demod_exit(dsp->mf_demod); + dsp->mf_demod = NULL; + } +} + +/* + * tone encoder + */ + +static struct dsp_digits { + char tone; + uint32_t mask; +} dsp_digits[] = { + { '1', 0x01 + 0x02 }, + { '2', 0x01 + 0x04 }, + { '3', 0x02 + 0x04 }, + { '4', 0x01 + 0x08 }, + { '5', 0x02 + 0x08 }, + { '6', 0x04 + 0x08 }, + { '7', 0x01 + 0x10 }, + { '8', 0x02 + 0x10 }, + { '9', 0x04 + 0x10 }, + { '0', 0x08 + 0x10 }, + { '*', 0x01 + 0x20 }, /* code 11 */ + { '#', 0x02 + 0x20 }, /* code 12 */ + { 'a', 0x04 + 0x20 }, /* KP1 */ + { 'b', 0x08 + 0x20 }, /* KP2 */ + { 'c', 0x10 + 0x20 }, /* ST */ + { 'A', 0x40 }, /* 2400 answer, acknowledge */ + { 'B', 0x80 }, /* 2600 busy */ + { 'C', 0x40 + 0x80 }, /* 2600+2400 clear forward */ + { ' ', 0 }, /* silence */ + { 0 , 0 }, +}; + +#define KP_DIGIT_DURATION 0.100 +#define OTHER_DIGIT_DURATION 0.055 +#define DIGIT_PAUSE 0.055 + +/* set signaling tone duration threshold */ +void set_sig_detect_duration(dsp_t *dsp, double duration_AB, double duration_C) +{ + dsp->detect_count = 0; + dsp->sig_detect_duration_AB = (int)(1000.0 * duration_AB); + dsp->sig_detect_duration_C = (int)(1000.0 * duration_C); +} + +/* set given tone with duration (ms) or continuously (0) */ +void set_tone(dsp_t *dsp, char tone, double duration) +{ + int i; + + dsp->tone = 0; + + if (!tone) { + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Remove tone\n"); + return; + } + + for (i = 0; dsp_digits[i].tone; i++) { + if (dsp_digits[i].tone == tone) { + dsp->tone_mask = dsp_digits[i].mask; + dsp->tone = tone; + dsp->tone_duration = (int)(dsp->samplerate * duration); + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Set tone=\'%c\' duration=%.0fms (mask = 0x%02x)\n", dsp->tone, 1000.0 * duration, dsp->tone_mask); + return; + } + } +} + +/* get next tone from dial string, if any */ +static void get_tone_from_dial_string(dsp_t *dsp) +{ + char tone; + double duration; + + dsp->tone = 0; + + if (dsp->dial_index == dsp->dial_length) { + dsp->dial_length = 0; + dialing_complete(dsp->priv); + return; + } + + /* get alternating tone/pause from dial string */ + if (!dsp->digit_pause) { + /* digit on */ + tone = dsp->dial_string[dsp->dial_index++]; + dsp->digit_pause = 1; + if (tone == 'a' || tone == 'b') + duration = KP_DIGIT_DURATION; + else + duration = OTHER_DIGIT_DURATION; + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Send digit \'%c\' from dial string\n", tone); + } else { + /* digit pause */ + tone = ' '; + dsp->digit_pause = 0; + duration = DIGIT_PAUSE; + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Send pause after digit from dial string\n"); + } + + set_tone(dsp, tone, duration); +} + +/* set given dial string */ +void set_dial_string(dsp_t *dsp, const char *dial) +{ + dsp->digit_pause = 0; + strncpy(dsp->dial_string, dial, sizeof(dsp->dial_string) - 1); + dsp->dial_index = 0; + dsp->dial_length = strlen(dsp->dial_string); +} + +/* determine which tones to be modulated, get next tone, if elapsed */ +static int assemble_tones(dsp_t *dsp, uint32_t *mask, int length) +{ + int i; + + for (i = 0; i < length; i++) { + /* if tone was done, try to get next digit */ + if (!dsp->tone) { + if (!dsp->dial_length) + return i; + get_tone_from_dial_string(dsp); + if (!dsp->tone) + return i; + } + *mask++ = dsp->tone_mask; + if (dsp->tone_duration) { + /* count down duration, if tones is not continuous */ + if (!(--dsp->tone_duration)) + dsp->tone = 0; + } + } + + return i; +} + +/* + * tone deencoder + */ + +/* detection array for one frequency */ +static char decode_one[8] = + { ' ', ' ', ' ', ' ', ' ', ' ', 'A', 'B' }; /* A = 2400, B = 2600 */ + +/* detection matrix for two frequencies */ +static char decode_two[8][8] = +{ + { ' ', '1', '2', '4', '7', '*', ' ', ' ' }, /* * = code 11 */ + { '1', ' ', '3', '5', '8', '#', ' ', ' ' }, /* # = code 12 */ + { '2', '3', ' ', '6', '9', 'a', ' ', ' ' }, /* a = KP1 */ + { '4', '5', '6', ' ', '0', 'b', ' ', ' ' }, /* b = KP2 */ + { '7', '8', '9', '0', ' ', 'c', ' ', ' ' }, /* c = ST */ + { '*', '#', 'a', 'b', 'c', ' ', ' ', ' ' }, + { ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'C' }, /* C = 2600+2400 */ + { ' ', ' ', ' ', ' ', ' ', ' ', 'C', ' ' } +}; + +#define NONE_MIN_LEVEL_SQUARED + +/* determine which tone is played */ +static void detect_tones(dsp_t *dsp, sample_t *samples, sample_t **levels_squared, int length, int incoming) +{ + int f1, f2; + double f1_level_squared, f2_level_squared; + char tone; + int s, t; + + for (s = 0; s < length; s++) { + /* mute if split duration reached */ + if (dsp->split_duration && dsp->split_count == dsp->split_duration) + samples[s] = 0.0; + /* only perform tone detection every millisecond */ + dsp->detect_interval += dsp->ms_per_sample; + if (dsp->detect_interval < 1.0) + continue; + dsp->detect_interval -= 1.0; + + if (incoming) { +#ifdef DEBUG_DEMODULATOR + for (t = 0; t < dsp->mf_demod->tones; t++) { + char level[20]; + int db; + memset(level, 32, sizeof(level)); + db = roundf(level2db(sqrt(levels_squared[t][s]) * SPEECH_LEVEL) + 25); + if (db >= 0 && db < (int)sizeof(level)) + level[db] = '*'; + level[sizeof(level)-1]=0; + printf("%s|", level); + } + printf("\n"); +#endif + } + + /* find the tone which is the loudest */ + f1 = -1; + f1_level_squared = -1.0; + for (t = 0; t < dsp->mf_demod->tones; t++) { + if (levels_squared[t][s] > f1_level_squared) { + f1_level_squared = levels_squared[t][s]; + f1 = t; + } + } + /* find the tone which is the second loudest */ + f2 = -1; + f2_level_squared = -1.0; + for (t = 0; t < dsp->mf_demod->tones; t++) { + if (t == f1) + continue; + if (levels_squared[t][s] > f2_level_squared) { + f2_level_squared = levels_squared[t][s]; + f2 = t; + } + } + /* now check if the minimum level is reached */ + if (f1 >= 0 && f1_level_squared < tone_min_ampl_sq[f1]) + f1 = -1; + if (f2 >= 0 && f2_level_squared < tone_min_ampl_sq[f2]) + f2 = -1; + +// printf("%s f1=%.0f (%.1f dBm) f2=%.0f (%.1f dBm)\n", CHAN, (f1 >= 0) ? tone_freq[f1] : 0, level2db(sqrt(f1_level_squared) * SPEECH_LEVEL), (f2 >= 0) ? tone_freq[f2] : 0, level2db(sqrt(f2_level_squared) * SPEECH_LEVEL)); + /* check if no, one or two tones are detected */ + if (f1 < 0) + tone = ' '; + else if (f2 < 0) + tone = decode_one[f1]; + else { + if (f2_level_squared * tone_diff_ampl_sq[f2] < f1_level_squared) + tone = ' '; + else + tone = decode_two[f1][f2]; + } + //printf("tone=%c\n", tone); + + /* process interrupt counting, keep tone until interrupt counter expires */ + if (dsp->detect_tone != ' ' && tone != dsp->detect_tone) { + if (dsp->interrupt_count < dsp->interrupt_duration) { + dsp->interrupt_count++; + tone = dsp->detect_tone; + } + } else + dsp->interrupt_count = 0; + + /* split audio, after minimum duration of detecting a tone */ + if (tone >= 'A' && tone <= 'C') { + if (dsp->split_count < dsp->split_duration) + dsp->split_count++; + } else + dsp->split_count = 0; + + + /* some change in tone */ + if (dsp->detect_tone != tone) { + if (dsp->detect_count == 0) + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected new tone '%c' (%.1f dBm)\n", tone, level2db(sqrt(f1_level_squared) * SPEECH_LEVEL)); + switch (tone) { + case 'A': + case 'B': + /* tone appears, wait some time */ + if (dsp->detect_count < dsp->sig_detect_duration_AB) + dsp->detect_count++; + else { + /* sign tone detected */ + dsp->detect_count = 0; + dsp->detect_tone = tone; + receive_digit(dsp->priv, tone, level2db(sqrt(f1_level_squared) * SPEECH_LEVEL)); + } + break; + case 'C': + /* tone appears, wait some time */ + if (dsp->detect_count < dsp->sig_detect_duration_C) + dsp->detect_count++; + else { + /* sign tone detected */ + dsp->detect_count = 0; + dsp->detect_tone = tone; + receive_digit(dsp->priv, tone, level2db(sqrt(f1_level_squared) * SPEECH_LEVEL)); + } + break; + case ' ': + /* tone appears or ceases */ + dsp->detect_count = 0; + dsp->detect_tone = tone; + receive_digit(dsp->priv, tone, 0.0); + break; + default: + /* tone appears, wait some time */ + if (dsp->detect_count < dsp->mf_detect_duration) + dsp->detect_count++; + else { + /* sign tone detected */ + dsp->detect_count = 0; + dsp->detect_tone = tone; + receive_digit(dsp->priv, tone, level2db(sqrt(f1_level_squared) * SPEECH_LEVEL)); + } + } + } else + dsp->detect_count = 0; + } +} + +/* process audio from one link (source) to another (destination) */ +static void process_audio(ss5_t *ss5_a, ss5_t *ss5_b, int length) +{ + sample_t samples[2][length], s; + sample_t b1[length], b2[length], b3[length], b4[length], b5[length], b6[length], b7[length], b8[length]; + sample_t *levels_squared[NUM_TONES] = { b1, b2, b3, b4, b5, b6, b7, b8 }; + uint32_t mask[length]; + int16_t data[160]; + int count1, count2; + int i; + + /* trigger reception of RTP stuff */ + if (ss5_a->cc_session) + osmo_cc_session_handle(ss5_a->cc_session); + if (ss5_b->cc_session) + osmo_cc_session_handle(ss5_b->cc_session); + + /* get audio from jitter buffer */ + jitter_load(&ss5_a->dejitter, samples[0], length); + jitter_load(&ss5_b->dejitter, samples[1], length); + + /* optionally add comfort noise */ + if (!ss5_a->cc_callref && ss5_a->ss5_ep->comfort_noise) { + for (i = 0; i < length; i++) + samples[0][i] += (double)((int8_t)random()) / 2000.0; + } + if (!ss5_b->cc_callref && ss5_b->ss5_ep->comfort_noise) { + for (i = 0; i < length; i++) + samples[1][i] += (double)((int8_t)random()) / 2000.0; + } + + /* modulate tone/digit. if no tone has to be played (or it stopped), count is less than length */ + count1 = assemble_tones(&ss5_a->dsp, mask, length); + mf_mod(ss5_a->dsp.mf_mod, mask, samples[0], count1); + count2 = assemble_tones(&ss5_b->dsp, mask, length); + mf_mod(ss5_b->dsp.mf_mod, mask, samples[1], count2); + + /* optionally add some crosstalk */ + if (ss5_a->ss5_ep->crosstalk) { + /* use count, since it carries number of samples with signalling */ + for (i = 0; i < count1; i++) + samples[1][i] += samples[0][i] / 70.0; + } + if (ss5_b->ss5_ep->crosstalk) { + /* use count, since it carries number of samples with signalling */ + for (i = 0; i < count2; i++) + samples[0][i] += samples[1][i] / 70.0; + } + + /* ! here is the bridge from a to b and from b to a ! */ + + /* optionally add one way delay */ + if (ss5_b->delay_buffer) { + for (i = 0; i < length; i++) { + s = ss5_b->delay_buffer[ss5_b->delay_index]; + ss5_b->delay_buffer[ss5_b->delay_index] = samples[0][i]; + if (++(ss5_b->delay_index) == ss5_b->delay_length) + ss5_b->delay_index = 0; + samples[0][i] = s; + } + } + if (ss5_a->delay_buffer) { + for (i = 0; i < length; i++) { + s = ss5_a->delay_buffer[ss5_a->delay_index]; + ss5_a->delay_buffer[ss5_a->delay_index] = samples[1][i]; + if (++(ss5_a->delay_index) == ss5_a->delay_length) + ss5_a->delay_index = 0; + samples[1][i] = s; + } + } + + /* demodulate and call tone detector */ + mf_demod(ss5_b->dsp.mf_demod, samples[0], length, levels_squared); + detect_tones(&ss5_b->dsp, samples[0], levels_squared, length, 1); + mf_demod(ss5_a->dsp.mf_demod, samples[1], length, levels_squared); + detect_tones(&ss5_a->dsp, samples[1], levels_squared, length, 0); + + /* forward audio to CC if call exists */ + if (ss5_b->cc_callref && ss5_b->codec) { + samples_to_int16(data, samples[0], length); + osmo_cc_rtp_send(ss5_b->codec, (uint8_t *)data, length * 2, 1, length); + } + if (ss5_a->cc_callref && ss5_a->codec) { + samples_to_int16(data, samples[1], length); + osmo_cc_rtp_send(ss5_a->codec, (uint8_t *)data, length * 2, 1, length); + } +} + +/* clock is called every given number of samples (20ms) */ +void audio_clock(ss5_endpoint_t *ss5_ep_sunset, ss5_endpoint_t *ss5_ep_sunrise, int len) +{ + ss5_t *ss5_a, *ss5_b; + + if (!ss5_ep_sunset) + return; + + if (!ss5_ep_sunrise) { + /* each pair of links on the same endpoint are bridged */ + for (ss5_b = ss5_ep_sunset->link_list; ss5_b; ss5_b = ss5_b->next) { + ss5_a = ss5_b; + ss5_b = ss5_b->next; + if (!ss5_b) + break; + process_audio(ss5_a, ss5_b, len); + } + } else { + /* each link on two endpoints are bridged */ + for (ss5_a = ss5_ep_sunset->link_list, ss5_b = ss5_ep_sunrise->link_list; ss5_a && ss5_b; ss5_a = ss5_a->next, ss5_b = ss5_b->next) { + process_audio(ss5_a, ss5_b, len); + } + } +} + +/* take audio from CC and store in jitter buffer */ +void down_audio(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len) +{ + ss5_t *ss5 = codec->media->session->priv; + sample_t samples[len / 2]; + + int16_to_samples(samples, (int16_t *)data, len / 2); + jitter_save(&ss5->dejitter, samples, len / 2); +} + +void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint16_t *src = (uint16_t *)src_data, *dst; + int len = src_len / 2, i; + + dst = malloc(len * 2); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = htons(src[i]); + *dst_data = (uint8_t *)dst; + *dst_len = len * 2; +} + +void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint16_t *src = (uint16_t *)src_data, *dst; + int len = src_len / 2, i; + + dst = malloc(len * 2); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = ntohs(src[i]); + *dst_data = (uint8_t *)dst; + *dst_len = len * 2; +} + |