diff options
author | Andreas Eversberg <jolly@eversberg.eu> | 2016-02-16 18:56:55 +0100 |
---|---|---|
committer | Andreas Eversberg <jolly@eversberg.eu> | 2016-04-24 15:19:00 +0200 |
commit | 16acdbf59d8b56f759efad3ad86c75b7e472b4cd (patch) | |
tree | 4d68ffd4b8c1d96652a6bc507eaf743d044b924b /src/cnetz/dsp.c | |
parent | 4b3e3385b5d7e4017e699a6fe399db2bcae064cf (diff) |
Implementation of C-Netz (German mobile telephone system)
Diffstat (limited to 'src/cnetz/dsp.c')
-rw-r--r-- | src/cnetz/dsp.c | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/src/cnetz/dsp.c b/src/cnetz/dsp.c new file mode 100644 index 0000000..27bd0e4 --- /dev/null +++ b/src/cnetz/dsp.c @@ -0,0 +1,690 @@ +/* C-Netz audio processing + * + * (C) 2016 by Andreas Eversberg <jolly@eversberg.eu> + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <errno.h> +#include "../common/debug.h" +#include "../common/timer.h" +#include "cnetz.h" +#include "sysinfo.h" +#include "telegramm.h" +#include "dsp.h" + +/* test function to mirror received audio from ratio back to radio */ +//#define TEST_SCRABLE +/* test the audio quality after cascading two scramblers (TEST_SCRABLE must be defined) */ +//#define TEST_UNSCRABLE + +#define PI M_PI + +#define BITRATE 5280.0 /* bits per second */ +#define BLOCK_BITS 198 /* duration of one time slot including pause at beginning and end */ + +#ifdef TEST_SCRABLE +jitter_t scrambler_test_jb; +scrambler_t scrambler_test_scrambler1; +scrambler_t scrambler_test_scrambler2; +#endif + +static int16_t ramp_up[256], ramp_down[256]; + +void dsp_init(void) +{ +} + +static void dsp_init_ramp(cnetz_t *cnetz) +{ + double c; + int i; + int16_t deviation = cnetz->fsk_deviation; + + PDEBUG(DDSP, DEBUG_DEBUG, "Generating smooth ramp table.\n"); + for (i = 0; i < 256; i++) { + c = cos((double)i / 256.0 * PI); +#if 0 + if (c < 0) + c = -sqrt(-c); + else + c = sqrt(c); +#endif + ramp_down[i] = (int)(c * (double)deviation); + ramp_up[i] = -ramp_down[i]; + } +} + +/* Init transceiver instance. */ +int dsp_init_sender(cnetz_t *cnetz, int measure_speed, double clock_speed[2], double deviation, double noise) +{ + int rc = 0; + double size; + + PDEBUG(DDSP, DEBUG_DEBUG, "Init FSK for 'Sender'.\n"); + + if (measure_speed) { + cnetz->measure_speed = measure_speed; + cant_recover = 1; + } + + if (clock_speed[0] > 1000 || clock_speed[0] < -1000 || clock_speed[1] > 1000 || clock_speed[1] < -1000) { + PDEBUG(DDSP, DEBUG_ERROR, "Clock speed %.1f,%.1f ppm out of range! Plese use range between +-1000 ppm!\n", clock_speed[0], clock_speed[1]); + return -EINVAL; + } + PDEBUG(DDSP, DEBUG_INFO, "Using clock speed of %.1f ppm (RX) and %.1f ppm (TX) to correct sound card's clock.\n", clock_speed[0], clock_speed[1]); + + cnetz->fsk_bitduration = (double)cnetz->sender.samplerate / ((double)BITRATE / (1.0 + clock_speed[1] / 1000000.0)); + cnetz->fsk_tx_bitstep = 1.0 / cnetz->fsk_bitduration; + PDEBUG(DDSP, DEBUG_DEBUG, "Use %.4f samples for one bit duration @ %d.\n", cnetz->fsk_bitduration, cnetz->sender.samplerate); + + size = cnetz->fsk_bitduration * (double)BLOCK_BITS * 16.0; /* 16 blocks for distributed frames */ + cnetz->fsk_tx_buffer_size = size * 1.1; /* more to compensate clock speed */ + cnetz->fsk_tx_buffer = calloc(sizeof(int16_t), cnetz->fsk_tx_buffer_size); + if (!cnetz->fsk_tx_buffer) { + PDEBUG(DDSP, DEBUG_DEBUG, "No memory!\n"); + rc = -ENOMEM; + goto error; + } + + /* create devation and ramp */ + if (deviation > 1.0) + deviation = 1.0; + cnetz->fsk_deviation = (int16_t)(deviation * 32766.9); /* be sure not to overflow -32767 .. 32767 */ + dsp_init_ramp(cnetz); + cnetz->fsk_noise = noise; + + /* create speech buffer */ + cnetz->dsp_speech_buffer = calloc(sizeof(int16_t), cnetz->sender.samplerate); /* buffer is greater than sr/1.1, just to be secure */ + if (!cnetz->dsp_speech_buffer) { + PDEBUG(DDSP, DEBUG_DEBUG, "No memory!\n"); + rc = -ENOMEM; + goto error; + } + + /* reinit the sample rate to shrink/expand audio */ + init_samplerate(&cnetz->sender.srstate, (double)cnetz->sender.samplerate / 1.1); /* 66 <-> 60 */ + + rc = fsk_fm_init(&cnetz->fsk_demod, cnetz, cnetz->sender.samplerate, (double)BITRATE / (1.0 + clock_speed[0] / 1000000.0)); + if (rc < 0) + goto error; + + /* init scrambler for shrinked audio */ + scrambler_setup(&cnetz->scrambler_tx, (double)cnetz->sender.samplerate / 1.1); + scrambler_setup(&cnetz->scrambler_rx, (double)cnetz->sender.samplerate / 1.1); + + /* reinit jitter buffer for 8000 kHz */ + jitter_destroy(&cnetz->sender.audio); + rc = jitter_create(&cnetz->sender.audio, 8000 / 5); + if (rc < 0) + goto error; + + /* init compander, according to C-Netz specs, attack and recovery time + * shall not exceed according to ITU G.162 */ + init_compander(&cnetz->cstate, 8000, 5.0, 22.5, 32767); + +#ifdef TEST_SCRABLE + rc = jitter_create(&scrambler_test_jb, cnetz->sender.samplerate / 5); + if (rc < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to init jitter buffer for scrambler test!\n"); + exit(0); + } + scrambler_setup(&scrambler_test_scrambler1, cnetz->sender.samplerate); + scrambler_setup(&scrambler_test_scrambler2, cnetz->sender.samplerate); +#endif + + return 0; + +error: + dsp_cleanup_sender(cnetz); + + return rc; +} + +void dsp_cleanup_sender(cnetz_t *cnetz) +{ + PDEBUG(DDSP, DEBUG_DEBUG, "Cleanup FSK for 'Sender'.\n"); + + if (cnetz->fsk_tx_buffer) + free(cnetz->fsk_tx_buffer); + if (cnetz->dsp_speech_buffer) + free(cnetz->dsp_speech_buffer); +} + +/* receive sample time and calculate speed against system clock + * tx: indicates transmit stream + * result: if set the actual signal speed is used (instead of sample rate) */ +void calc_clock_speed(cnetz_t *cnetz, uint64_t samples, int tx, int result) +{ + struct clock_speed *cs = &cnetz->clock_speed; + double ti; + double speed_ppm_rx[2], speed_ppm_tx[2]; + + if (!cnetz->measure_speed) + return; + + if (result) + tx += 2; + + ti = get_time(); + + /* skip some time to avoid false mesurement due to filling of buffers */ + if (cs->meas_ti == 0.0) { + cs->meas_ti = ti + 1.0; + return; + } + if (cs->meas_ti > ti) + return; + + /* start sample counting */ + if (cs->start_ti[tx] == 0.0) { + cs->start_ti[tx] = ti; + cs->spl_count[tx] = 0; + return; + } + + /* add elapsed time */ + cs->last_ti[tx] = ti; + cs->spl_count[tx] += samples; + + /* only calculate speed, if one second has elapsed */ + if (ti - cs->meas_ti <= 1.0) + return; + cs->meas_ti += 1.0; + + if (!cs->spl_count[2] || !cs->spl_count[3]) + return; + speed_ppm_rx[0] = ((double)cs->spl_count[0] / (double)cnetz->sender.samplerate) / (cs->last_ti[0] - cs->start_ti[0]) * 1000000.0 - 1000000.0; + speed_ppm_tx[0] = ((double)cs->spl_count[1] / (double)cnetz->sender.samplerate) / (cs->last_ti[1] - cs->start_ti[1]) * 1000000.0 - 1000000.0; + speed_ppm_rx[1] = ((double)cs->spl_count[2] / (double)cnetz->sender.samplerate) / (cs->last_ti[2] - cs->start_ti[2]) * 1000000.0 - 1000000.0; + speed_ppm_tx[1] = ((double)cs->spl_count[3] / (double)cnetz->sender.samplerate) / (cs->last_ti[3] - cs->start_ti[3]) * 1000000.0 - 1000000.0; + PDEBUG(DDSP, DEBUG_NOTICE, "Clock: RX=%.2f TX=%.2f; Signal: TX=%.2f RX=%.2f ppm\n", speed_ppm_rx[0], speed_ppm_tx[0], speed_ppm_rx[1], speed_ppm_tx[1]); +} + +static int fsk_nothing_encode(cnetz_t *cnetz) +{ + int16_t *spl; + double phase, bitstep, r; + int i, count; + + spl = cnetz->fsk_tx_buffer; + phase = cnetz->fsk_tx_phase; + bitstep = cnetz->fsk_tx_bitstep * 256.0; + + if (cnetz->fsk_noise) { + r = cnetz->fsk_noise; + /* add 198 bits of noise */ + for (i = 0; i < 198; i++) { + do { + *spl++ = (double)((int16_t)(random() & 0xffff)) * r; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + } else { + /* add 198 bits of silence */ + for (i = 0; i < 198; i++) { + do { + *spl++ = 0; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + } + + /* depending on the number of samples, return the number */ + count = ((uintptr_t)spl - (uintptr_t)cnetz->fsk_tx_buffer) / sizeof(*spl); + + cnetz->fsk_tx_phase = phase; + cnetz->fsk_tx_buffer_length = count; + + return count; +} + +/* encode one data block into samples + * input: 184 data bits (including barker code) + * output: samples + * return number of samples */ +static int fsk_block_encode(cnetz_t *cnetz, const char *bits) +{ + /* alloc samples, add 1 in case there is a rest */ + int16_t *spl; + double phase, bitstep, deviation; + int i, count; + char last; + + deviation = cnetz->fsk_deviation; + spl = cnetz->fsk_tx_buffer; + phase = cnetz->fsk_tx_phase; + bitstep = cnetz->fsk_tx_bitstep * 256.0; + + /* add 7 bits of pause */ + for (i = 0; i < 7; i++) { + do { + *spl++ = 0; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + /* add 184 bits */ + last = ' '; + for (i = 0; i < 184; i++) { + switch (last) { + case ' ': + if (bits[i] == '1') { + /* ramp up from 0 */ + do { + *spl++ = ramp_up[(int)phase] / 2 + deviation / 2; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } else { + /* ramp down from 0 */ + do { + *spl++ = ramp_down[(int)phase] / 2 - deviation / 2; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + break; + case '1': + if (bits[i] == '1') { + /* stay up */ + do { + *spl++ = deviation; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } else { + /* ramp down */ + do { + *spl++ = ramp_down[(int)phase]; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + break; + case '0': + if (bits[i] == '1') { + /* ramp up */ + do { + *spl++ = ramp_up[(int)phase]; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } else { + /* stay down */ + do { + *spl++ = -deviation; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + break; + } + last = bits[i]; + } + /* add 7 bits of pause */ + if (last == '0') { + /* ramp up to 0 */ + do { + *spl++ = ramp_up[(int)phase] / 2 - deviation / 2; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } else { + /* ramp down to 0 */ + do { + *spl++ = ramp_down[(int)phase] / 2 + deviation / 2; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + for (i = 1; i < 7; i++) { + do { + *spl++ = 0; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + + /* depending on the number of samples, return the number */ + count = ((uintptr_t)spl - (uintptr_t)cnetz->fsk_tx_buffer) / sizeof(*spl); + + cnetz->fsk_tx_phase = phase; + cnetz->fsk_tx_buffer_length = count; + + return count; +} + +/* encode one distributed data block into samples + * input: 184 data bits (including barker code) + * output: samples + * if a sample contains 0x8000, it indicates where to insert speech block + * return number of samples */ +static int fsk_distributed_encode(cnetz_t *cnetz, const char *bits) +{ + /* alloc samples, add 1 in case there is a rest */ + int16_t *spl, *marker; + double phase, bitstep, deviation; + int i, j, count; + char last; + + deviation = cnetz->fsk_deviation; + spl = cnetz->fsk_tx_buffer; + phase = cnetz->fsk_tx_phase; + bitstep = cnetz->fsk_tx_bitstep * 256.0; + + /* add 2 * (1+4+1 + 60) bits of pause / for speech */ + for (i = 0; i < 2; i++) { + for (j = 0; j < 6; j++) { + do { + *spl++ = 0; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + marker = spl; + for (j = 0; j < 60; j++) { + do { + *spl++ = 0; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + *marker = -32768; /* indicator for inserting speech */ + } + /* add 46 * (1+4+1 + 60) bits */ + for (i = 0; i < 46; i++) { + /* unmodulated bit */ + do { + *spl++ = 0; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + last = ' '; + for (j = 0; j < 4; j++) { + switch (last) { + case ' ': + if (bits[i * 4 + j] == '1') { + /* ramp up from 0 */ + do { + *spl++ = ramp_up[(int)phase] / 2 + deviation / 2; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } else { + /* ramp down from 0 */ + do { + *spl++ = ramp_down[(int)phase] / 2 - deviation / 2; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + break; + case '1': + if (bits[i * 4 + j] == '1') { + /* stay up */ + do { + *spl++ = deviation; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } else { + /* ramp down */ + do { + *spl++ = ramp_down[(int)phase]; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + break; + case '0': + if (bits[i * 4 + j] == '1') { + /* ramp up */ + do { + *spl++ = ramp_up[(int)phase]; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } else { + /* stay down */ + do { + *spl++ = -deviation; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + break; + } + last = bits[i * 4 + j]; + } + /* unmodulated bit */ + if (last == '0') { + /* ramp up to 0 */ + do { + *spl++ = ramp_up[(int)phase] / 2 - deviation / 2; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } else { + /* ramp down to 0 */ + do { + *spl++ = ramp_down[(int)phase] / 2 + deviation / 2; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + marker = spl; + for (j = 0; j < 60; j++) { + do { + *spl++ = 0; + phase += bitstep; + } while (phase < 256.0); + phase -= 256.0; + } + *marker = -32768; /* indicator for inserting speech */ + } + + /* depending on the number of samples, return the number */ + count = ((uintptr_t)spl - (uintptr_t)cnetz->fsk_tx_buffer) / sizeof(*spl); + + cnetz->fsk_tx_phase = phase; + cnetz->fsk_tx_buffer_length = count; + + return count; +} + +void show_level(double level) +{ + char text[42] = " "; + + if (level > 1.0) + level = 1.0; + if (level < -1.0) + level = -1.0; + text[20 - (int)(level * 20)] = '*'; + printf("%s\n", text); +} + +/* decode samples and hut for bit changes + * use deviation to find greatest slope of the signal (bit change) + */ +void sender_receive(sender_t *sender, int16_t *samples, int length) +{ + cnetz_t *cnetz = (cnetz_t *) sender; + + /* measure rx sample speed */ + calc_clock_speed(cnetz, length, 0, 0); + +#ifdef TEST_SCRABLE +#ifdef TEST_UNSCRABLE + scrambler(&scrambler_test_scrambler1, samples, length); +#endif + jitter_save(&scrambler_test_jb, samples, length); + return; +#endif + + fsk_fm_demod(&cnetz->fsk_demod, samples, length); + return; +} + +static int fsk_telegramm(cnetz_t *cnetz, int16_t *samples, int length) +{ + int count = 0, pos, copy, i, speech_length, speech_pos; + int16_t *spl, *speech_buffer; + const char *bits; + + speech_buffer = cnetz->dsp_speech_buffer; + speech_length = cnetz->dsp_speech_length; + speech_pos = cnetz->dsp_speech_pos; + +again: + /* there must be length, otherwise we would skip blocks */ + if (!length) + return count; + + pos = cnetz->fsk_tx_buffer_pos; + spl = cnetz->fsk_tx_buffer + pos; + + /* start new telegramm, so we generate one */ + if (pos == 0) { + /* measure actual signal speed */ + if (cnetz->sched_ts == 0 && cnetz->sched_r_m == 0) + calc_clock_speed(cnetz, cnetz->sender.samplerate * 24 / 10, 1, 1); + + /* switch to speech channel */ + if (cnetz->sched_switch_mode && cnetz->sched_r_m == 0) { + if (--cnetz->sched_switch_mode == 0) { + /* OgK / SpK(K) / SpK(V) */ + PDEBUG(DDSP, DEBUG_INFO, "Switching channel (mode)\n"); + cnetz->dsp_mode = cnetz->sched_dsp_mode; + } + } + + switch (cnetz->dsp_mode) { + case DSP_MODE_OGK: + if (((1 << cnetz->sched_ts) & si.ogk_timeslot_mask)) { + if (cnetz->sched_r_m == 0) { + /* set last time slot, so we can match received message from mobile station */ + cnetz->last_tx_timeslot = cnetz->sched_ts; + PDEBUG(DDSP, DEBUG_DEBUG, "Transmitting 'Rufblock' at timeslot %d\n", cnetz->sched_ts); + bits = cnetz_encode_telegramm(cnetz); + } else { + PDEBUG(DDSP, DEBUG_DEBUG, "Transmitting 'Meldeblock' at timeslot %d\n", cnetz->sched_ts); + bits = cnetz_encode_telegramm(cnetz); + } + fsk_block_encode(cnetz, bits); + } else { + fsk_nothing_encode(cnetz); + } + break; + case DSP_MODE_SPK_K: + PDEBUG(DDSP, DEBUG_DEBUG, "Transmitting 'Konzentrierte Signalisierung'\n"); + bits = cnetz_encode_telegramm(cnetz); + fsk_block_encode(cnetz, bits); + break; + case DSP_MODE_SPK_V: + PDEBUG(DDSP, DEBUG_DEBUG, "Transmitting 'Verteilte Signalisierung'\n"); + bits = cnetz_encode_telegramm(cnetz); + fsk_distributed_encode(cnetz, bits); + break; + default: + fsk_nothing_encode(cnetz); + } + + if (cnetz->dsp_mode == DSP_MODE_SPK_V) { + /* count sub frame */ + cnetz->sched_ts += 8; + } else { + /* count slot */ + if (cnetz->sched_r_m == 0) + cnetz->sched_r_m = 1; + else { + cnetz->sched_r_m = 0; + cnetz->sched_ts++; + } + } + if (cnetz->sched_ts == 32) + cnetz->sched_ts = 0; + } + + copy = cnetz->fsk_tx_buffer_length - pos; + if (length < copy) + copy = length; + for (i = 0; i < copy; i++) { + if (*spl == -32768) { + /* marker found to insert new chunk of audio */ + jitter_load(&cnetz->sender.audio, speech_buffer, 100); + compress_audio(&cnetz->cstate, speech_buffer, 100); + speech_length = samplerate_upsample(&cnetz->sender.srstate, speech_buffer, 100, speech_buffer); + if (cnetz->scrambler) + scrambler(&cnetz->scrambler_tx, speech_buffer, speech_length); + /* pre-emphasis is done by cnetz code, not by common code */ + /* pre-emphasis makes bad sound in conjunction with scrambler, so we disable */ + if (cnetz->pre_emphasis && !cnetz->scrambler) + pre_emphasis(&cnetz->estate, speech_buffer, speech_length); + speech_pos = 0; + } + /* copy speech as long as we have something left in buffer */ + if (speech_pos < speech_length) + *samples++ = speech_buffer[speech_pos++]; + else + *samples++ = *spl; + spl++; + } + cnetz->dsp_speech_length = speech_length; + cnetz->dsp_speech_pos = speech_pos; + pos += copy; + count += copy; + length -= copy; + if (pos == cnetz->fsk_tx_buffer_length) { + cnetz->fsk_tx_buffer_pos = 0; + goto again; + } + + cnetz->fsk_tx_buffer_pos = pos; + + return count; +} + +/* Provide stream of audio toward radio unit */ +void sender_send(sender_t *sender, int16_t *samples, int length) +{ + cnetz_t *cnetz = (cnetz_t *) sender; + int count; + + /* measure tx sample speed */ + calc_clock_speed(cnetz, length, 1, 0); + +#ifdef TEST_SCRABLE + jitter_load(&scrambler_test_jb, samples, length); + scrambler(&scrambler_test_scrambler2, samples, length); + return; +#endif + + count = fsk_telegramm(cnetz, samples, length); + if (count < length) { + printf("length=%d < count=%d\n", length, count); + printf("this shall not happen, so please fix!\n"); + exit(0); + } +} + |