forked from cellular-infrastructure/osmocom-analog
MTS/IMTS: (Improved) Mobile Telephone Service
Implementation of the 0G Mobile Phone Network of US and Canada MTS or IMTS mode is selectable, als well as 5 or 7 digit mode.
This commit is contained in:
parent
31fca59294
commit
99bafb6880
|
@ -60,6 +60,8 @@ src/amps/amps
|
||||||
src/tacs/tacs
|
src/tacs/tacs
|
||||||
src/jtacs/jtacs
|
src/jtacs/jtacs
|
||||||
src/r2000/radiocom2000
|
src/r2000/radiocom2000
|
||||||
|
src/imts/imts
|
||||||
|
src/imts/imts-dialer
|
||||||
src/jolly/jollycom
|
src/jolly/jollycom
|
||||||
src/tv/osmotv
|
src/tv/osmotv
|
||||||
src/radio/osmoradio
|
src/radio/osmoradio
|
||||||
|
|
1
README
1
README
|
@ -13,6 +13,7 @@ generated simultaniously using SDR. Currently supported networks:
|
||||||
* TACS (Total Access Communication System)
|
* TACS (Total Access Communication System)
|
||||||
* JTACS (Japanese version of TACS)
|
* JTACS (Japanese version of TACS)
|
||||||
* Radiocom 2000 (French network)
|
* Radiocom 2000 (French network)
|
||||||
|
* IMTS / MTS ((Improved) Mobile Telephone Service)
|
||||||
* JollyCom (Unofficial network, invented by the author)
|
* JollyCom (Unofficial network, invented by the author)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,7 @@ AC_OUTPUT(
|
||||||
src/tacs/Makefile
|
src/tacs/Makefile
|
||||||
src/jtacs/Makefile
|
src/jtacs/Makefile
|
||||||
src/r2000/Makefile
|
src/r2000/Makefile
|
||||||
|
src/imts/Makefile
|
||||||
src/jolly/Makefile
|
src/jolly/Makefile
|
||||||
src/tv/Makefile
|
src/tv/Makefile
|
||||||
src/radio/Makefile
|
src/radio/Makefile
|
||||||
|
|
|
@ -60,6 +60,7 @@ It started with the idea to make a base station for the German B-Netz, but more
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This project is pure software that requires a transmitter and a receiver connected to the sound card of a Linux PC.
|
This project is pure software that requires a transmitter and a receiver connected to the sound card of a Linux PC.
|
||||||
|
Alternatively (more suggested) you can use a full duplex SDR to even generate multiple channels at a time.
|
||||||
A second sound card or ISDN card is used to route calls from and to the mobile phone.
|
A second sound card or ISDN card is used to route calls from and to the mobile phone.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ SUBDIRS += \
|
||||||
tacs \
|
tacs \
|
||||||
jtacs \
|
jtacs \
|
||||||
r2000 \
|
r2000 \
|
||||||
|
imts \
|
||||||
jolly \
|
jolly \
|
||||||
tv \
|
tv \
|
||||||
radio \
|
radio \
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||||
|
|
||||||
|
bin_PROGRAMS = \
|
||||||
|
imts \
|
||||||
|
imts-dialer
|
||||||
|
|
||||||
|
imts_SOURCES = \
|
||||||
|
imts.c \
|
||||||
|
dsp.c \
|
||||||
|
image.c \
|
||||||
|
main.c
|
||||||
|
imts_LDADD = \
|
||||||
|
$(COMMON_LA) \
|
||||||
|
../amps/libusatone.a \
|
||||||
|
$(top_builddir)/src/liboptions/liboptions.a \
|
||||||
|
$(top_builddir)/src/libdebug/libdebug.a \
|
||||||
|
$(top_builddir)/src/libmobile/libmobile.a \
|
||||||
|
$(top_builddir)/src/libdisplay/libdisplay.a \
|
||||||
|
$(top_builddir)/src/libjitter/libjitter.a \
|
||||||
|
$(top_builddir)/src/libsquelch/libsquelch.a \
|
||||||
|
$(top_builddir)/src/libtimer/libtimer.a \
|
||||||
|
$(top_builddir)/src/libsamplerate/libsamplerate.a \
|
||||||
|
$(top_builddir)/src/libemphasis/libemphasis.a \
|
||||||
|
$(top_builddir)/src/libfm/libfm.a \
|
||||||
|
$(top_builddir)/src/libfilter/libfilter.a \
|
||||||
|
$(top_builddir)/src/libwave/libwave.a \
|
||||||
|
$(top_builddir)/src/libmncc/libmncc.a \
|
||||||
|
$(top_builddir)/src/libsample/libsample.a \
|
||||||
|
-lm
|
||||||
|
|
||||||
|
imts_dialer_SOURCES = \
|
||||||
|
dialer.c
|
||||||
|
|
||||||
|
imts_dialer_LDADD = \
|
||||||
|
$(COMMON_LA) \
|
||||||
|
$(top_builddir)/src/liboptions/liboptions.a \
|
||||||
|
$(top_builddir)/src/libdebug/libdebug.a \
|
||||||
|
$(top_builddir)/src/libwave/libwave.a \
|
||||||
|
$(top_builddir)/src/libsample/libsample.a \
|
||||||
|
-lm
|
||||||
|
|
||||||
|
if HAVE_ALSA
|
||||||
|
imts_LDADD += \
|
||||||
|
$(top_builddir)/src/libsound/libsound.a \
|
||||||
|
$(ALSA_LIBS)
|
||||||
|
|
||||||
|
imts_dialer_LDADD += \
|
||||||
|
$(top_builddir)/src/libsound/libsound.a \
|
||||||
|
$(ALSA_LIBS)
|
||||||
|
endif
|
||||||
|
|
||||||
|
if HAVE_SDR
|
||||||
|
imts_LDADD += \
|
||||||
|
$(top_builddir)/src/libsdr/libsdr.a \
|
||||||
|
$(top_builddir)/src/libfft/libfft.a \
|
||||||
|
$(UHD_LIBS) \
|
||||||
|
$(SOAPY_LIBS)
|
||||||
|
endif
|
||||||
|
|
||||||
|
if HAVE_ALSA
|
||||||
|
AM_CPPFLAGS += -DHAVE_ALSA
|
||||||
|
endif
|
||||||
|
|
|
@ -0,0 +1,354 @@
|
||||||
|
/* IMTS dialer
|
||||||
|
*
|
||||||
|
* (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 <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include "../libsample/sample.h"
|
||||||
|
#include "../libwave/wave.h"
|
||||||
|
#include "../libdebug/debug.h"
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
#include "../libsound/sound.h"
|
||||||
|
#endif
|
||||||
|
#include "../liboptions/options.h"
|
||||||
|
|
||||||
|
/* presets */
|
||||||
|
const char *station_id = "6681739";
|
||||||
|
const char *dialing;
|
||||||
|
const char *audiodev = "hw:0,0";
|
||||||
|
int samplerate = 48000;
|
||||||
|
const char *write_tx_wave = NULL;
|
||||||
|
int latency = 50;
|
||||||
|
|
||||||
|
#define TONE_DONE -1
|
||||||
|
#define TONE_SILENCE 0
|
||||||
|
#define TONE_GUARD 2150
|
||||||
|
#define TONE_CONNECT 1633
|
||||||
|
#define TONE_DISCONNECT 1336
|
||||||
|
|
||||||
|
/* states */
|
||||||
|
static struct dial_string {
|
||||||
|
char console;
|
||||||
|
int tone;
|
||||||
|
int length;
|
||||||
|
} dial_string[2048];
|
||||||
|
static int dial_pos = 0;
|
||||||
|
static int latspl;
|
||||||
|
|
||||||
|
/* instances */
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
void *audio = NULL;
|
||||||
|
#endif
|
||||||
|
wave_rec_t wave_tx_rec;
|
||||||
|
|
||||||
|
/* dummy functions */
|
||||||
|
int num_kanal = 1; /* only one channel used for debugging */
|
||||||
|
void display_status_limit_scroll() {}
|
||||||
|
void *get_sender_by_empfangsfrequenz() { return NULL; }
|
||||||
|
void display_measurements_add() {}
|
||||||
|
void display_measurements_update() {}
|
||||||
|
|
||||||
|
static void print_help(const char *arg0)
|
||||||
|
{
|
||||||
|
printf("Usage: %s [options] <number> | disconnect\n\n", arg0);
|
||||||
|
/* - - */
|
||||||
|
printf("This program generates a dialing sequence to make a call via IMTS base\n");
|
||||||
|
printf("station using an amateur radio transceiver.\n");
|
||||||
|
printf("Also it can write an audio file (wave) to be fed into IMTS base station for\n");
|
||||||
|
printf("showing how it simulates an IMTS phone doing an outgoing call.\n\n");
|
||||||
|
printf("Use 'disconnect' to generate a disconnect sequence.\n\n");
|
||||||
|
printf(" -h --help\n");
|
||||||
|
printf(" This help\n");
|
||||||
|
printf(" -i --station-id <station ID>\n");
|
||||||
|
printf(" 7 Digits of ID of mobile station (default = '%s')\n", station_id);
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
printf(" -a --audio-device hw:<card>,<device>\n");
|
||||||
|
printf(" Sound card and device number (default = '%s')\n", audiodev);
|
||||||
|
#endif
|
||||||
|
printf(" -s --samplerate <rate>\n");
|
||||||
|
printf(" Sample rate of sound device (default = '%d')\n", samplerate);
|
||||||
|
printf(" -w --write-tx-wave <file>\n");
|
||||||
|
printf(" Write audio to given wave file also.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_options(void)
|
||||||
|
{
|
||||||
|
option_add('h', "help", 0);
|
||||||
|
option_add('i', "station-id", 1);
|
||||||
|
option_add('a', "audio-device", 1);
|
||||||
|
option_add('s', "samplerate", 1);
|
||||||
|
option_add('w', "write-tx-wave", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_options(int short_option, int __attribute__((unused)) argi, char **argv)
|
||||||
|
{
|
||||||
|
switch (short_option) {
|
||||||
|
case 'h':
|
||||||
|
print_help(argv[0]);
|
||||||
|
return 0;
|
||||||
|
case 'i':
|
||||||
|
station_id = strdup(argv[argi]);
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
audiodev = strdup(argv[argi]);
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
samplerate = atoi(argv[argi]);
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
write_tx_wave = strdup(argv[argi]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double phase = 0;
|
||||||
|
|
||||||
|
/* encode audio */
|
||||||
|
static void encode_audio(sample_t *samples, uint8_t *power, int length)
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
memset(power, 1, length);
|
||||||
|
|
||||||
|
again:
|
||||||
|
count = length;
|
||||||
|
if (dial_string[dial_pos].length && dial_string[dial_pos].length < count)
|
||||||
|
count = dial_string[dial_pos].length;
|
||||||
|
|
||||||
|
switch (dial_string[dial_pos].tone) {
|
||||||
|
case TONE_DONE:
|
||||||
|
case TONE_SILENCE:
|
||||||
|
memset(samples, 0, count * sizeof(*samples));
|
||||||
|
phase = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
samples[i] = cos(2.0 * M_PI * (double)dial_string[dial_pos].tone * phase);
|
||||||
|
phase += 1.0 / samplerate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dial_string[dial_pos].length) {
|
||||||
|
dial_string[dial_pos].length -= count;
|
||||||
|
if (dial_string[dial_pos].length == 0) {
|
||||||
|
if (dial_string[dial_pos].console) {
|
||||||
|
printf("%c", dial_string[dial_pos].console);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
dial_pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
samples += count;
|
||||||
|
length -= count;
|
||||||
|
|
||||||
|
if (length)
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* loop that gets audio from encoder and fowards it to sound card.
|
||||||
|
* alternatively a sound file is written.
|
||||||
|
*/
|
||||||
|
static void process_signal(void)
|
||||||
|
{
|
||||||
|
sample_t buff[latspl], *samples[1] = { buff };
|
||||||
|
uint8_t pbuff[latspl], *power[1] = { pbuff };
|
||||||
|
int count;
|
||||||
|
int __attribute__((unused)) rc;
|
||||||
|
|
||||||
|
while (dial_string[dial_pos].tone != TONE_DONE) {
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
count = sound_get_tosend(audio, latspl);
|
||||||
|
#else
|
||||||
|
count = samplerate / 1000;
|
||||||
|
#endif
|
||||||
|
if (count < 0) {
|
||||||
|
PDEBUG(DDSP, DEBUG_ERROR, "Failed to get number of samples in buffer (rc = %d)!\n", count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* encode dial_string of tones and lengths */
|
||||||
|
encode_audio(samples[0], power[0], count);
|
||||||
|
|
||||||
|
/* write wave, if open */
|
||||||
|
if (wave_tx_rec.fp)
|
||||||
|
wave_write(&wave_tx_rec, samples, count);
|
||||||
|
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
/* write audio */
|
||||||
|
rc = sound_write(audio, samples, power, count, NULL, NULL, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
PDEBUG(DDSP, DEBUG_ERROR, "Failed to write TX data to audio device (rc = %d)\n", rc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* sleep a while */
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int i, d, p, pulses, tone = 0;
|
||||||
|
int rc, argi;
|
||||||
|
|
||||||
|
memset(dial_string, 0, sizeof(dial_string));
|
||||||
|
|
||||||
|
/* latency of send buffer in samples */
|
||||||
|
latspl = samplerate * latency / 1000;
|
||||||
|
|
||||||
|
/* handle options / config file */
|
||||||
|
add_options();
|
||||||
|
argi = options_command_line(argc, argv, handle_options);
|
||||||
|
if (argi <= 0)
|
||||||
|
return argi;
|
||||||
|
|
||||||
|
if (argi >= argc) {
|
||||||
|
printf("No phone number given!\n\n");
|
||||||
|
print_help(argv[0]);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for valid station ID */
|
||||||
|
if (strlen(station_id) != 7) {
|
||||||
|
printf("Given station ID '%s' has invalid number of digits!\n", station_id);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
for (i = 0; station_id[i]; i++) {
|
||||||
|
if (station_id[i] < '0' || station_id[i] > '9') {
|
||||||
|
printf("Given station ID '%s' has invalid digits!\n", station_id);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialing = argv[argi];
|
||||||
|
d = 0;
|
||||||
|
dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 0.600 * (double)samplerate; /* pause */
|
||||||
|
if (!!strcasecmp(dialing, "disconnect")) {
|
||||||
|
/* check for valid phone number */
|
||||||
|
if (strlen(dialing) > 64) {
|
||||||
|
printf("Given phone number '%s' has too many digits! (more than allowed 64 digits)\n", dialing);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
for (i = 0; dialing[i]; i++) {
|
||||||
|
if (dialing[i] < '0' || dialing[i] > '9') {
|
||||||
|
printf("Given phone number '%s' has invalid digits!\n", dialing);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.350 * (double)samplerate; /* off-hook */
|
||||||
|
dial_string[d].console = 's';
|
||||||
|
dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.050 * (double)samplerate; /* seize */
|
||||||
|
dial_string[d].console = '-';
|
||||||
|
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 1.000 * (double)samplerate; /* pause */
|
||||||
|
for (i = 0; station_id[i]; i++) {
|
||||||
|
pulses = station_id[i] - '0';
|
||||||
|
if (pulses == 0)
|
||||||
|
pulses = 10;
|
||||||
|
dial_string[d].console = station_id[i];
|
||||||
|
for (p = 1; p <= pulses; p++) {
|
||||||
|
if ((p & 1) == 1)
|
||||||
|
tone = TONE_SILENCE;
|
||||||
|
else
|
||||||
|
tone = TONE_GUARD;
|
||||||
|
dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.025 * (double)samplerate; /* mark */
|
||||||
|
dial_string[d].tone = tone; dial_string[d++].length = 0.025 * (double)samplerate; /* space */
|
||||||
|
}
|
||||||
|
dial_string[d].tone = tone; dial_string[d++].length = 0.190 * (double)samplerate; /* after digit */
|
||||||
|
}
|
||||||
|
dial_string[d].console = '-';
|
||||||
|
dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 2.000 * (double)samplerate; /* pause */
|
||||||
|
for (i = 0; dialing[i]; i++) {
|
||||||
|
pulses = dialing[i] - '0';
|
||||||
|
if (pulses == 0)
|
||||||
|
pulses = 10;
|
||||||
|
dial_string[d].console = dialing[i];
|
||||||
|
for (p = 1; p <= pulses; p++) {
|
||||||
|
dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.060 * (double)samplerate; /* mark */
|
||||||
|
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.040 * (double)samplerate; /* space */
|
||||||
|
}
|
||||||
|
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.400 * (double)samplerate; /* after digit */
|
||||||
|
}
|
||||||
|
dial_string[d].console = '\n';
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < 750; i += 50) {
|
||||||
|
dial_string[d].tone = TONE_DISCONNECT; dial_string[d++].length = 0.025 * (double)samplerate; /* mark */
|
||||||
|
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.025 * (double)samplerate; /* space */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 0.600 * (double)samplerate; /* pause */
|
||||||
|
dial_string[d].tone = TONE_DONE; dial_string[d++].length = 0; /* end */
|
||||||
|
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
/* init sound */
|
||||||
|
audio = sound_open(audiodev, NULL, NULL, 1, 0.0, samplerate, latspl, 1.0, 4000.0);
|
||||||
|
if (!audio) {
|
||||||
|
PDEBUG(DBNETZ, DEBUG_ERROR, "No sound device!\n");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* open wave */
|
||||||
|
if (write_tx_wave) {
|
||||||
|
rc = wave_create_record(&wave_tx_rec, write_tx_wave, samplerate, 1, 1.0);
|
||||||
|
if (rc < 0) {
|
||||||
|
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifndef HAVE_ALSA
|
||||||
|
else {
|
||||||
|
PDEBUG(DBNETZ, DEBUG_ERROR, "No sound support compiled in, so you need to write to a wave file. See help!\n");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
/* start sound */
|
||||||
|
sound_start(audio);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PDEBUG(DBNETZ, DEBUG_ERROR, "Start audio after pause...\n");
|
||||||
|
|
||||||
|
process_signal();
|
||||||
|
|
||||||
|
exit:
|
||||||
|
/* close wave */
|
||||||
|
wave_destroy_record(&wave_tx_rec);
|
||||||
|
|
||||||
|
#ifdef HAVE_ALSA
|
||||||
|
/* exit sound */
|
||||||
|
if (audio)
|
||||||
|
sound_close(audio);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,564 @@
|
||||||
|
/* MTS/IMTS signal processing
|
||||||
|
*
|
||||||
|
* (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define CHAN imts->sender.kanal
|
||||||
|
|
||||||
|
#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 "../libtimer/timer.h"
|
||||||
|
#include "../libmobile/call.h"
|
||||||
|
#include "imts.h"
|
||||||
|
#include "dsp.h"
|
||||||
|
|
||||||
|
/* uncomment to debug decoder */
|
||||||
|
//#define DEBUG_DECODER
|
||||||
|
|
||||||
|
#define PI 3.1415927
|
||||||
|
|
||||||
|
/* signaling */
|
||||||
|
#define MAX_DEVIATION 7000.0 /* signaling tone plus some extra to calibrate */
|
||||||
|
#define MAX_MODULATION 3000.0 /* FIXME */
|
||||||
|
#define DBM0_DEVIATION 2500.0 /* deviation of dBm0 (with emphasis) */
|
||||||
|
#define TX_PEAK_TONE (5000.0 / DBM0_DEVIATION) /* signaling tone level (5khz, no emphasis) */
|
||||||
|
#define RX_MIN_AMPL 0.25 /* FIXME: Minimum level to detect tone */
|
||||||
|
/* Note that 75 is half of the distance between two tones (2000 and 2150 Hz)
|
||||||
|
* An error of more than 50 causes too much toggeling between two tones,
|
||||||
|
* less would take too long to detect the tone and maybe not detect it, if
|
||||||
|
* it is too far off the expected frequency.
|
||||||
|
*/
|
||||||
|
#define RX_MIN_FREQ 50.0 /* minimum frequency error to detect tone */
|
||||||
|
#define MAX_DISPLAY (MAX_DEVIATION / DBM0_DEVIATION)/* as much as MAX_DEVIATION */
|
||||||
|
/* Note that FILTER_BW / SUSTAIN and QUAL_TIME sum up and should not exeed minimum tone length */
|
||||||
|
#define RX_FILTER_BW 100.0 /* amplitude filter (causes delay) */
|
||||||
|
#define RX_SUSTAIN 0.010 /* how long a tone must sustain until detected (causes delay) */
|
||||||
|
#define RX_QUAL_TIME 0.005 /* how long a quality measurement lasts after detecting a tone */
|
||||||
|
|
||||||
|
/* carrier loss detection */
|
||||||
|
#define MUTE_TIME 0.1 /* time to mute after loosing signal */
|
||||||
|
#define DELAY_TIME 0.15 /* delay, so we don't hear the noise before squelch mutes (MTS mode) */
|
||||||
|
#define LOSS_TIME 12.0 /* duration of signal loss before release (FIXME: what was the actual duration ???) */
|
||||||
|
#define SIGNAL_DETECT 0.3 /* time to have a steady signal to detect phone (used by MTS) */
|
||||||
|
|
||||||
|
#define DISPLAY_INTERVAL 0.04
|
||||||
|
|
||||||
|
/* signaling tones to be modulated */
|
||||||
|
static double tones[NUM_SIG_TONES] = {
|
||||||
|
2150.0, /* guard */
|
||||||
|
2000.0, /* idle */
|
||||||
|
1800.0, /* seize */
|
||||||
|
1633.0, /* connect */
|
||||||
|
1336.0, /* disconnect */
|
||||||
|
600.0, /* MTS 600 Hz */
|
||||||
|
1500.0, /* MTS 1500 Hz */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* signaling tones to be demodulated (a subset of tones above) */
|
||||||
|
static double tone_response[NUM_SIG_TONES] = {
|
||||||
|
0.71, /* guard */
|
||||||
|
1.08, /* idle */
|
||||||
|
1.01, /* seize */
|
||||||
|
1.04, /* connect */
|
||||||
|
0.71, /* disconnect */
|
||||||
|
0.71, /* MTS 600 Hz */
|
||||||
|
0.71, /* MTS 1500 Hz */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* all tone, with signaling tones first */
|
||||||
|
static const char *tone_names[] = {
|
||||||
|
"GUARD tone",
|
||||||
|
"IDLE tone",
|
||||||
|
"SEIZE tone",
|
||||||
|
"CONNECT tone",
|
||||||
|
"DISCONNECT tone",
|
||||||
|
"600 Hz Tone",
|
||||||
|
"1500 Hz Tone",
|
||||||
|
"SILENCE",
|
||||||
|
"NOISE",
|
||||||
|
"DIALTONE",
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DIALTONE1 350.0
|
||||||
|
#define DIALTONE2 440.0
|
||||||
|
|
||||||
|
/* table for fast sine generation */
|
||||||
|
static sample_t dsp_sine_tone[65536];
|
||||||
|
|
||||||
|
/* global init for audio processing */
|
||||||
|
void dsp_init(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
double s;
|
||||||
|
|
||||||
|
PDEBUG(DDSP, DEBUG_DEBUG, "Generating sine tables.\n");
|
||||||
|
for (i = 0; i < 65536; i++) {
|
||||||
|
s = sin((double)i / 65536.0 * 2.0 * PI);
|
||||||
|
dsp_sine_tone[i] = s * TX_PEAK_TONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Init transceiver instance. */
|
||||||
|
int dsp_init_transceiver(imts_t *imts, double squelch_db, int ptt)
|
||||||
|
{
|
||||||
|
int rc = -1;
|
||||||
|
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for Transceiver.\n");
|
||||||
|
|
||||||
|
imts->sample_duration = 1.0 / (double)imts->sender.samplerate;
|
||||||
|
|
||||||
|
/* init squelch */
|
||||||
|
squelch_init(&imts->squelch, imts->sender.kanal, squelch_db, MUTE_TIME, LOSS_TIME);
|
||||||
|
|
||||||
|
/* set modulation parameters */
|
||||||
|
sender_set_fm(&imts->sender, MAX_DEVIATION, MAX_MODULATION, DBM0_DEVIATION, MAX_DISPLAY);
|
||||||
|
|
||||||
|
/* init FM demodulator for tone detection */
|
||||||
|
if (imts->mode == MODE_IMTS) {
|
||||||
|
imts->demod_center = (tones[TONE_GUARD] + tones[TONE_DISCONNECT]) / 2.0;
|
||||||
|
imts->demod_bandwidth = tones[TONE_GUARD] - tones[TONE_DISCONNECT];
|
||||||
|
} else {
|
||||||
|
imts->demod_center = (tones[TONE_1500] + tones[TONE_600]) / 2.0;
|
||||||
|
imts->demod_bandwidth = tones[TONE_1500] - tones[TONE_600];
|
||||||
|
}
|
||||||
|
rc = fm_demod_init(&imts->demod, (double)imts->sender.samplerate, imts->demod_center, imts->demod_bandwidth);
|
||||||
|
if (rc < 0)
|
||||||
|
goto error;
|
||||||
|
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
|
||||||
|
/* NOTE: CHANGE TONE RESPONSES ABOVE, IF YOU CHANGE FILTER */
|
||||||
|
iir_lowpass_init(&imts->demod_freq_lp, RX_FILTER_BW, (double)imts->sender.samplerate, 2);
|
||||||
|
iir_lowpass_init(&imts->demod_ampl_lp, RX_FILTER_BW, (double)imts->sender.samplerate, 2);
|
||||||
|
|
||||||
|
/* tones */
|
||||||
|
imts->tone_idle_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_IDLE]);
|
||||||
|
imts->tone_seize_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_SEIZE]);
|
||||||
|
imts->tone_600_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_600]);
|
||||||
|
imts->tone_1500_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_1500]);
|
||||||
|
imts->tone_dialtone_phaseshift65536[0] = 65536.0 / ((double)imts->sender.samplerate / DIALTONE1);
|
||||||
|
imts->tone_dialtone_phaseshift65536[1] = 65536.0 / ((double)imts->sender.samplerate / DIALTONE2);
|
||||||
|
|
||||||
|
/* demod init */
|
||||||
|
imts->demod_current_tone = TONE_SILENCE;
|
||||||
|
imts->demod_sig_tone = 0;
|
||||||
|
imts->demod_last_tone = TONE_SILENCE;
|
||||||
|
|
||||||
|
/* delay buffer */
|
||||||
|
if (ptt) {
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Push to talk: Adding delay buffer to remove noise when singal gets lost.\n");
|
||||||
|
imts->delay_max = (int)((double)imts->sender.samplerate * DELAY_TIME);
|
||||||
|
imts->delay_spl = calloc(imts->delay_max, sizeof(*imts->delay_spl));
|
||||||
|
if (!imts->delay_spl) {
|
||||||
|
PDEBUG(DDSP, DEBUG_ERROR, "No mem for delay buffer!\n");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imts->dmp_tone_level = display_measurements_add(&imts->sender.dispmeas, "Tone Level", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0);
|
||||||
|
imts->dmp_tone_quality = display_measurements_add(&imts->sender.dispmeas, "Tone Quality", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
dsp_cleanup_transceiver(imts);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cleanup transceiver instance. */
|
||||||
|
void dsp_cleanup_transceiver(imts_t *imts)
|
||||||
|
{
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for Transceiver.\n");
|
||||||
|
|
||||||
|
fm_demod_exit(&imts->demod);
|
||||||
|
if (imts->delay_spl) {
|
||||||
|
free(imts->delay_spl);
|
||||||
|
imts->delay_spl = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate audio stream from tone. Keep phase for next call of function. */
|
||||||
|
static int generate_tone(imts_t *imts, sample_t *samples, int length)
|
||||||
|
{
|
||||||
|
double phaseshift[2] = {0,0}, phase[2]; // make gcc happy
|
||||||
|
int i;
|
||||||
|
|
||||||
|
switch (imts->tone) {
|
||||||
|
case TONE_IDLE:
|
||||||
|
phaseshift[0] = imts->tone_idle_phaseshift65536;
|
||||||
|
break;
|
||||||
|
case TONE_SEIZE:
|
||||||
|
phaseshift[0] = imts->tone_seize_phaseshift65536;
|
||||||
|
break;
|
||||||
|
case TONE_600:
|
||||||
|
phaseshift[0] = imts->tone_600_phaseshift65536;
|
||||||
|
break;
|
||||||
|
case TONE_1500:
|
||||||
|
phaseshift[0] = imts->tone_1500_phaseshift65536;
|
||||||
|
break;
|
||||||
|
case TONE_DIALTONE:
|
||||||
|
phaseshift[0] = imts->tone_dialtone_phaseshift65536[0];
|
||||||
|
phaseshift[1] = imts->tone_dialtone_phaseshift65536[1];
|
||||||
|
break;
|
||||||
|
case TONE_SILENCE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Software error, unsupported tone, please fix!\n");
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* don't send more than given length */
|
||||||
|
if (imts->tone_duration && length > imts->tone_duration)
|
||||||
|
length = imts->tone_duration;
|
||||||
|
|
||||||
|
phase[0] = imts->tone_phase65536[0];
|
||||||
|
phase[1] = imts->tone_phase65536[1];
|
||||||
|
|
||||||
|
switch (imts->tone) {
|
||||||
|
case TONE_SILENCE:
|
||||||
|
memset(samples, 0, length * sizeof(*samples));
|
||||||
|
phase[0] = phase[1] = 0;
|
||||||
|
break;
|
||||||
|
case TONE_DIALTONE:
|
||||||
|
for (i = 0; i < length; i++) {
|
||||||
|
*samples = dsp_sine_tone[(uint16_t)phase[0]];
|
||||||
|
*samples += dsp_sine_tone[(uint16_t)phase[1]];
|
||||||
|
*samples++ /= 4.0; /* not full volume */
|
||||||
|
phase[0] += phaseshift[0];
|
||||||
|
if (phase[0] >= 65536)
|
||||||
|
phase[0] -= 65536;
|
||||||
|
phase[1] += phaseshift[1];
|
||||||
|
if (phase[1] >= 65536)
|
||||||
|
phase[1] -= 65536;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
for (i = 0; i < length; i++) {
|
||||||
|
*samples++ = dsp_sine_tone[(uint16_t)phase[0]];
|
||||||
|
phase[0] += phaseshift[0];
|
||||||
|
if (phase[0] >= 65536)
|
||||||
|
phase[0] -= 65536;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imts->tone_phase65536[0] = phase[0];
|
||||||
|
imts->tone_phase65536[1] = phase[1];
|
||||||
|
|
||||||
|
/* if tone has been sent completely, tell IMTS call process */
|
||||||
|
if (imts->tone_duration) {
|
||||||
|
imts->tone_duration -= length;
|
||||||
|
if (imts->tone_duration == 0)
|
||||||
|
imts_tone_sent(imts, imts->tone);
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Provide stream of audio toward radio unit */
|
||||||
|
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||||
|
{
|
||||||
|
imts_t *imts = (imts_t *) sender;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
memset(power, 1, length);
|
||||||
|
|
||||||
|
again:
|
||||||
|
switch (imts->dsp_mode) {
|
||||||
|
case DSP_MODE_OFF:
|
||||||
|
memset(power, 0, length);
|
||||||
|
memset(samples, 0, length * sizeof(*samples));
|
||||||
|
break;
|
||||||
|
case DSP_MODE_TONE:
|
||||||
|
memset(power, 1, length);
|
||||||
|
count = generate_tone(imts, samples, length);
|
||||||
|
samples += count;
|
||||||
|
length -= count;
|
||||||
|
if (length)
|
||||||
|
goto again;
|
||||||
|
break;
|
||||||
|
case DSP_MODE_AUDIO:
|
||||||
|
memset(power, 1, length);
|
||||||
|
jitter_load(&imts->sender.dejitter, samples, length);
|
||||||
|
if (imts->pre_emphasis)
|
||||||
|
pre_emphasis(&imts->estate, samples, length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tone_demod(imts_t *imts, sample_t *samples, int length)
|
||||||
|
{
|
||||||
|
sample_t frequency[length], amplitude[length];
|
||||||
|
sample_t I[length], Q[length];
|
||||||
|
double f, amp;
|
||||||
|
int i, t, tone = 0; // make GCC happy
|
||||||
|
|
||||||
|
/* demod frquency */
|
||||||
|
fm_demodulate_real(&imts->demod, frequency, length, samples, I, Q);
|
||||||
|
iir_process(&imts->demod_freq_lp, frequency, length);
|
||||||
|
|
||||||
|
/* demod amplitude
|
||||||
|
* 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[i] = sqrt(I[i] * I[i] + Q[i] * Q[i]) * 2.0;
|
||||||
|
iir_process(&imts->demod_ampl_lp, amplitude, length);
|
||||||
|
|
||||||
|
/* check result to detect tone or loss or change */
|
||||||
|
for (i = 0; i < length; i++) {
|
||||||
|
/* duration until change in tone */
|
||||||
|
imts->demod_duration += imts->sample_duration;
|
||||||
|
/* correct FSK amplitude */
|
||||||
|
amplitude[i] /= TX_PEAK_TONE;
|
||||||
|
/* see what we detect at this moment; tone is set */
|
||||||
|
if (amplitude[i] < RX_MIN_AMPL) {
|
||||||
|
/* silence */
|
||||||
|
tone = TONE_SILENCE;
|
||||||
|
} else {
|
||||||
|
/* tone */
|
||||||
|
f = frequency[i] + imts->demod_center;
|
||||||
|
if (imts->mode == MODE_IMTS) {
|
||||||
|
for (t = TONE_IDLE; t <= TONE_DISCONNECT; t++) {
|
||||||
|
/* check for frequency */
|
||||||
|
if (fabs(f - tones[t]) < RX_MIN_FREQ) {
|
||||||
|
tone = t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (t = TONE_600; t <= TONE_1500; t++) {
|
||||||
|
/* check for frequency */
|
||||||
|
if (fabs(f - tones[t]) < RX_MIN_FREQ) {
|
||||||
|
tone = t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* noise */
|
||||||
|
if (t == NUM_SIG_TONES)
|
||||||
|
tone = TONE_NOISE;
|
||||||
|
}
|
||||||
|
#ifdef DEBUG_DECODER
|
||||||
|
/* debug decoder */
|
||||||
|
if (tone < NUM_SIG_TONES) {
|
||||||
|
static int debug_interval = 0;
|
||||||
|
if (++debug_interval == 30) {
|
||||||
|
debug_interval = 0;
|
||||||
|
printf("decoder debug: diff=%s %.0f ", debug_amplitude((f - tones[t]) / RX_MIN_FREQ), fabs(f - tones[t]));
|
||||||
|
amp = amplitude[i] / tone_response[t];
|
||||||
|
printf("ampl=%s %.0f%%\n", debug_amplitude(amp), amp * 100.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/* display level of tones, or zero at noise/silence */
|
||||||
|
imts->display_interval +=imts->sample_duration;
|
||||||
|
if (imts->display_interval >= DISPLAY_INTERVAL) {
|
||||||
|
if (tone < NUM_SIG_TONES)
|
||||||
|
amp = amplitude[i] / tone_response[tone];
|
||||||
|
else
|
||||||
|
amp = 0.0;
|
||||||
|
display_measurements_update(imts->dmp_tone_level, amp * 100.0, 0.0);
|
||||||
|
imts->display_interval -= DISPLAY_INTERVAL;
|
||||||
|
}
|
||||||
|
/* check for tone change */
|
||||||
|
if (tone != imts->demod_current_tone) {
|
||||||
|
#ifdef DEBUG_DECODER
|
||||||
|
printf("decoder debug: %s detected, waiting to sustain\n", tone_names[tone]);
|
||||||
|
#endif
|
||||||
|
if (imts->demod_sig_tone) {
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Lost %s (duration %.0f ms)\n", tone_names[imts->demod_current_tone], imts->demod_duration * 1000.0);
|
||||||
|
imts_lost_tone(imts, imts->demod_current_tone, imts->demod_duration);
|
||||||
|
imts->demod_sig_tone = 0;
|
||||||
|
}
|
||||||
|
imts->demod_current_tone = tone;
|
||||||
|
imts->demod_sustain = 0.0;
|
||||||
|
} else if (imts->demod_sustain < RX_SUSTAIN) {
|
||||||
|
imts->demod_sustain += imts->sample_duration;
|
||||||
|
/* when sustained. also tone must change to prevent flapping; lost signaling tone can be detected again */
|
||||||
|
if (imts->demod_sustain >= RX_SUSTAIN && (imts->demod_current_tone != imts->demod_last_tone || !imts->demod_sig_tone)) {
|
||||||
|
if (imts->demod_current_tone < NUM_SIG_TONES) {
|
||||||
|
amp = amplitude[i] / tone_response[imts->demod_current_tone];
|
||||||
|
imts->demod_sig_tone = 1;
|
||||||
|
imts->demod_quality_time = 0.0;
|
||||||
|
imts->demod_quality_count = 0;
|
||||||
|
imts->demod_quality_value = 0.0;
|
||||||
|
} else {
|
||||||
|
amp = amplitude[i];
|
||||||
|
imts->demod_sig_tone = 0;
|
||||||
|
}
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected %s (level %.0f%%)\n", tone_names[imts->demod_current_tone], amp * 100);
|
||||||
|
imts_receive_tone(imts, imts->demod_current_tone, imts->demod_duration, amp);
|
||||||
|
imts->demod_last_tone = imts->demod_current_tone;
|
||||||
|
imts->demod_duration = imts->demod_sustain;
|
||||||
|
} else if (imts->demod_sig_tone && imts->demod_quality_time < RX_QUAL_TIME) {
|
||||||
|
f = frequency[i] + imts->demod_center;
|
||||||
|
imts->demod_quality_time += imts->sample_duration;
|
||||||
|
imts->demod_quality_count++;
|
||||||
|
imts->demod_quality_value += fabs((f - tones[imts->demod_current_tone])) / RX_MIN_FREQ;
|
||||||
|
if (imts->demod_quality_time >= RX_QUAL_TIME) {
|
||||||
|
double quality = 1.0 - imts->demod_quality_value / (double)imts->demod_quality_count * 2.0;
|
||||||
|
if (quality < 0)
|
||||||
|
quality = 0;
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Quality: %.0f%%\n", quality * 100.0);
|
||||||
|
display_measurements_update(imts->dmp_tone_quality, quality * 100.0, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void delay_audio(imts_t *imts, sample_t *samples, int count)
|
||||||
|
{
|
||||||
|
sample_t *spl, s;
|
||||||
|
int pos, max;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
spl = imts->delay_spl;
|
||||||
|
pos = imts->delay_pos;
|
||||||
|
max = imts->delay_max;
|
||||||
|
|
||||||
|
/* feed audio though delay buffer */
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
s = samples[i];
|
||||||
|
samples[i] = spl[pos];
|
||||||
|
spl[pos] = s;
|
||||||
|
if (++pos == max)
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
imts->delay_pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Process received audio stream from radio unit. */
|
||||||
|
void sender_receive(sender_t *sender, sample_t *samples, int length, double rf_level_db)
|
||||||
|
{
|
||||||
|
imts_t *imts = (imts_t *) sender;
|
||||||
|
|
||||||
|
/* no processing if off */
|
||||||
|
if (imts->dsp_mode == DSP_MODE_OFF)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* process signal mute/loss, also for signalling tone */
|
||||||
|
switch (squelch(&imts->squelch, rf_level_db, (double)length * imts->sample_duration)) {
|
||||||
|
case SQUELCH_LOSS:
|
||||||
|
imts_loss_indication(imts, LOSS_TIME);
|
||||||
|
/* FALLTHRU */
|
||||||
|
case SQUELCH_MUTE:
|
||||||
|
if (imts->mode == MODE_MTS && !imts->is_mute) {
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_INFO, "Low RF level, muting.\n");
|
||||||
|
memset(imts->delay_spl, 0, sizeof(*samples) * imts->delay_max);
|
||||||
|
imts->is_mute = 1;
|
||||||
|
}
|
||||||
|
memset(samples, 0, sizeof(*samples) * length);
|
||||||
|
/* signal is gone */
|
||||||
|
imts->rf_signal = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (imts->is_mute) {
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_INFO, "High RF level, unmuting.\n");
|
||||||
|
imts->is_mute = 0;
|
||||||
|
}
|
||||||
|
/* detect signal, if it is steady for a while */
|
||||||
|
if (imts->rf_signal < SIGNAL_DETECT * (double)imts->sender.samplerate) {
|
||||||
|
/* only do this, if signal has not been detected yet */
|
||||||
|
imts->rf_signal += length;
|
||||||
|
if (imts->rf_signal >= SIGNAL_DETECT * (double)imts->sender.samplerate)
|
||||||
|
imts_signal_indication(imts);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FM/AM demod */
|
||||||
|
tone_demod(imts, samples, length);
|
||||||
|
|
||||||
|
/* delay audio to prevent noise before squelch mutes (don't do that for signaling tones) */
|
||||||
|
if (imts->delay_spl)
|
||||||
|
delay_audio(imts, samples, length);
|
||||||
|
|
||||||
|
/* Forward audio to network (call process). */
|
||||||
|
if (imts->dsp_mode == DSP_MODE_AUDIO && imts->callref) {
|
||||||
|
sample_t *spl;
|
||||||
|
int pos;
|
||||||
|
int count;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (imts->de_emphasis) {
|
||||||
|
dc_filter(&imts->estate, samples, length);
|
||||||
|
de_emphasis(&imts->estate, samples, length);
|
||||||
|
}
|
||||||
|
count = samplerate_downsample(&imts->sender.srstate, samples, length);
|
||||||
|
spl = imts->sender.rxbuf;
|
||||||
|
pos = imts->sender.rxbuf_pos;
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
spl[pos++] = samples[i];
|
||||||
|
if (pos == 160) {
|
||||||
|
call_up_audio(imts->callref, spl, 160);
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imts->sender.rxbuf_pos = pos;
|
||||||
|
} else
|
||||||
|
imts->sender.rxbuf_pos = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *imts_dsp_mode_name(enum dsp_mode mode)
|
||||||
|
{
|
||||||
|
static char invalid[16];
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case DSP_MODE_OFF:
|
||||||
|
return "TRX OFF";
|
||||||
|
case DSP_MODE_TONE:
|
||||||
|
return "TONE";
|
||||||
|
case DSP_MODE_AUDIO:
|
||||||
|
return "AUDIO";
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(invalid, "invalid(%d)", mode);
|
||||||
|
return invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void imts_set_dsp_mode(imts_t *imts, enum dsp_mode mode, int tone, double duration, int reset_demod)
|
||||||
|
{
|
||||||
|
/* reset demod */
|
||||||
|
if (reset_demod) {
|
||||||
|
imts->demod_current_tone = TONE_SILENCE;
|
||||||
|
imts->demod_sig_tone = 0;
|
||||||
|
imts->demod_last_tone = TONE_SILENCE;
|
||||||
|
imts->demod_duration = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imts->dsp_mode != mode) {
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "DSP mode %s -> %s\n", imts_dsp_mode_name(imts->dsp_mode), imts_dsp_mode_name(mode));
|
||||||
|
imts->dsp_mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == DSP_MODE_TONE) {
|
||||||
|
if (duration)
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Start sending %s for %.3f seconds.\n", tone_names[tone], duration);
|
||||||
|
else
|
||||||
|
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Start sending %s continuously.\n", tone_names[tone]);
|
||||||
|
imts->tone = tone;
|
||||||
|
imts->tone_duration = duration * (double)imts->sender.samplerate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
void dsp_init(void);
|
||||||
|
int dsp_init_transceiver(imts_t *imts, double squelch_db, int ptt);
|
||||||
|
void dsp_cleanup_transceiver(imts_t *imts);
|
||||||
|
void imts_set_dsp_mode(imts_t *imts, enum dsp_mode mode, int tone, double duration, int reset_demod);
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../libmobile/image.h"
|
||||||
|
|
||||||
|
const char *image[] = {
|
||||||
|
"@W",
|
||||||
|
"IMTS / MTS is back!",
|
||||||
|
"",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
void print_image(void)
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
for (i = 0; image[i]; i++) {
|
||||||
|
for (j = 0; j < (int)strlen(image[i]); j++) {
|
||||||
|
if (image[i][j] == '@') {
|
||||||
|
j++;
|
||||||
|
switch(image[i][j]) {
|
||||||
|
case 'k': /* black */
|
||||||
|
printf("\033[0;30m");
|
||||||
|
break;
|
||||||
|
case 'r': /* red */
|
||||||
|
printf("\033[0;31m");
|
||||||
|
break;
|
||||||
|
case 'g': /* green */
|
||||||
|
printf("\033[0;32m");
|
||||||
|
break;
|
||||||
|
case 'y': /* yellow */
|
||||||
|
printf("\033[0;33m");
|
||||||
|
break;
|
||||||
|
case 'b': /* blue */
|
||||||
|
printf("\033[0;34m");
|
||||||
|
break;
|
||||||
|
case 'm': /* magenta */
|
||||||
|
printf("\033[0;35m");
|
||||||
|
break;
|
||||||
|
case 'c': /* cyan */
|
||||||
|
printf("\033[0;36m");
|
||||||
|
break;
|
||||||
|
case 'w': /* white */
|
||||||
|
printf("\033[0;37m");
|
||||||
|
break;
|
||||||
|
case 'K': /* bright black */
|
||||||
|
printf("\033[1;30m");
|
||||||
|
break;
|
||||||
|
case 'R': /* bright red */
|
||||||
|
printf("\033[1;31m");
|
||||||
|
break;
|
||||||
|
case 'G': /* bright green */
|
||||||
|
printf("\033[1;32m");
|
||||||
|
break;
|
||||||
|
case 'Y': /* bright yellow */
|
||||||
|
printf("\033[1;33m");
|
||||||
|
break;
|
||||||
|
case 'B': /* bright blue */
|
||||||
|
printf("\033[1;34m");
|
||||||
|
break;
|
||||||
|
case 'M': /* bright magenta */
|
||||||
|
printf("\033[1;35m");
|
||||||
|
break;
|
||||||
|
case 'C': /* bright cyan */
|
||||||
|
printf("\033[1;36m");
|
||||||
|
break;
|
||||||
|
case 'W': /* bright white */
|
||||||
|
printf("\033[1;37m");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
printf("%c", image[i][j]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("\033[0;39m");
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,139 @@
|
||||||
|
#include "../libsquelch/squelch.h"
|
||||||
|
#include "../libfm/fm.h"
|
||||||
|
#include "../libmobile/sender.h"
|
||||||
|
|
||||||
|
enum dsp_mode {
|
||||||
|
DSP_MODE_OFF = 0, /* transmitter off */
|
||||||
|
DSP_MODE_TONE, /* send tone or silence */
|
||||||
|
DSP_MODE_AUDIO, /* send audio */
|
||||||
|
};
|
||||||
|
|
||||||
|
#define TONE_GUARD 0
|
||||||
|
#define TONE_IDLE 1
|
||||||
|
#define TONE_SEIZE 2
|
||||||
|
#define TONE_CONNECT 3
|
||||||
|
#define TONE_DISCONNECT 4
|
||||||
|
|
||||||
|
#define TONE_600 5
|
||||||
|
#define TONE_1500 6
|
||||||
|
|
||||||
|
#define TONE_SILENCE 7
|
||||||
|
#define TONE_NOISE 8
|
||||||
|
#define TONE_DIALTONE 9
|
||||||
|
|
||||||
|
#define NUM_SIG_TONES 7
|
||||||
|
|
||||||
|
enum mode {
|
||||||
|
MODE_IMTS = 0,
|
||||||
|
MODE_MTS,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum imts_state {
|
||||||
|
IMTS_NULL = 0,
|
||||||
|
/* channel is idle */
|
||||||
|
IMTS_OFF, /* base station not in use and turned off */
|
||||||
|
IMTS_IDLE, /* base station not in use and sending 2000 Hz Idle tone */
|
||||||
|
/* mobile originated */
|
||||||
|
IMTS_SEIZE, /* base station sends seize to acknowldege call from mobile */
|
||||||
|
IMTS_ANI, /* base station is receiving ANI from mobile */
|
||||||
|
IMTS_DIALING, /* base station is receiving dial digits */
|
||||||
|
/* mobile terminated */
|
||||||
|
IMTS_PAGING, /* base station is paging mobile */
|
||||||
|
IMTS_RINGING, /* base station is ringing mobile */
|
||||||
|
/* active call */
|
||||||
|
IMTS_CONVERSATION, /* base station and mobile have conversation */
|
||||||
|
/* releasing call */
|
||||||
|
IMTS_RELEASE, /* base station turned off */
|
||||||
|
/* loopback test */
|
||||||
|
IMTS_PAGING_TEST, /* loopback test sequence */
|
||||||
|
/* detector test */
|
||||||
|
IMTS_DETECTOR_TEST, /* detector test sequence */
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct imts {
|
||||||
|
sender_t sender;
|
||||||
|
|
||||||
|
/* channel's states */
|
||||||
|
enum imts_state state; /* current sender's state */
|
||||||
|
int pre_emphasis; /* use pre_emphasis by this instance */
|
||||||
|
int de_emphasis; /* use de_emphasis by this instance */
|
||||||
|
emphasis_t estate;
|
||||||
|
int callref; /* call reference */
|
||||||
|
char station_id[11]; /* current station ID (also used for test pattern) */
|
||||||
|
int station_length; /* digit length of station ID */
|
||||||
|
char dial_number[33]; /* number dialing */
|
||||||
|
struct timer timer;
|
||||||
|
int last_tone; /* last tone received */
|
||||||
|
double last_sigtone_amplitude; /* amplitude of last signaling tone received */
|
||||||
|
double fast_seize; /* fast seize: guard-length - roundtrip-delay */
|
||||||
|
double rx_guard_timestamp; /* start of guard tone (seize by mobile) */
|
||||||
|
double rx_guard_duration; /* duration of guard (only long guards are detected) */
|
||||||
|
int rx_ani_pulse; /* current pulse # receiving */
|
||||||
|
int rx_ani_index; /* current digit # receiving */
|
||||||
|
int rx_ani_totpulses; /* total pulses count receiving */
|
||||||
|
int rx_dial_pulse; /* current pulse # receiving */
|
||||||
|
int rx_dial_index; /* current digit # receiving */
|
||||||
|
int rx_disc_pulse; /* current pulse # receiving */
|
||||||
|
int tx_page_pulse; /* current pulse # transmitting */
|
||||||
|
int tx_page_index; /* current digit # transmitting */
|
||||||
|
double tx_page_timestamp; /* last pulse of digit transmitting */
|
||||||
|
int tx_ring_pulse; /* current pulse # transmitting */
|
||||||
|
int rx_page_pulse; /* current pulse # receiving */
|
||||||
|
double detector_test_length_1; /* detector test tone duration */
|
||||||
|
double detector_test_length_2; /* detector test tone duration */
|
||||||
|
double detector_test_length_3; /* detector test tone duration */
|
||||||
|
|
||||||
|
/* MTS additional states */
|
||||||
|
enum mode mode; /* set if MTS mode is used */
|
||||||
|
const char *operator; /* operator's number to call when seizing the channel */
|
||||||
|
|
||||||
|
/* display measurements */
|
||||||
|
dispmeasparam_t *dmp_tone_level;
|
||||||
|
dispmeasparam_t *dmp_tone_quality;
|
||||||
|
|
||||||
|
/* dsp states */
|
||||||
|
double sample_duration; /* 1 / samplerate */
|
||||||
|
double demod_center; /* center frequency for tone demodulation */
|
||||||
|
double demod_bandwidth; /* bandwidth for tone demodulation */
|
||||||
|
fm_demod_t demod; /* demodulator for frequency / amplitude */
|
||||||
|
iir_filter_t demod_freq_lp; /* filter for frequency response */
|
||||||
|
iir_filter_t demod_ampl_lp; /* filter for amplitude response */
|
||||||
|
int demod_current_tone; /* current tone being detected */
|
||||||
|
int demod_sig_tone; /* current tone is a signaling tone */
|
||||||
|
int demod_last_tone; /* last tone being detected */
|
||||||
|
double demod_sustain; /* how long a tone must sustain */
|
||||||
|
double demod_duration; /* duration of last tone */
|
||||||
|
double demod_quality_time; /* time counter to measure quality */
|
||||||
|
int demod_quality_count; /* counter to measure quality */
|
||||||
|
double demod_quality_value; /* sum of quality samples (must be divided by count) */
|
||||||
|
double display_interval; /* used to update tone levels */
|
||||||
|
enum dsp_mode dsp_mode; /* current mode: audio, durable tone 0 or 1, paging */
|
||||||
|
int ptt; /* set, if push to talk is used (transmitter of phone off) */
|
||||||
|
int tone; /* current tone to send */
|
||||||
|
int tone_duration; /* if set, tone is limited to this duration (in samples) */
|
||||||
|
double tone_idle_phaseshift65536;/* how much the phase of sine wave changes per sample */
|
||||||
|
double tone_seize_phaseshift65536;/* how much the phase of sine wave changes per sample */
|
||||||
|
double tone_600_phaseshift65536;/* how much the phase of sine wave changes per sample */
|
||||||
|
double tone_1500_phaseshift65536;/* how much the phase of sine wave changes per sample */
|
||||||
|
double tone_dialtone_phaseshift65536[2];/* how much the phase of sine wave changes per sample */
|
||||||
|
double tone_phase65536[2]; /* current phase */
|
||||||
|
squelch_t squelch; /* squelch detection process */
|
||||||
|
int is_mute; /* set if quelch has muted */
|
||||||
|
int rf_signal; /* set if we have currently an RF signal */
|
||||||
|
sample_t *delay_spl; /* delay buffer for delaying audio */
|
||||||
|
int delay_pos; /* position in delay buffer */
|
||||||
|
int delay_max; /* number of samples in delay buffer */
|
||||||
|
} imts_t;
|
||||||
|
|
||||||
|
|
||||||
|
void imts_list_channels(void);
|
||||||
|
double imts_channel2freq(const char *kanal, int uplink);
|
||||||
|
int imts_init(void);
|
||||||
|
int imts_create(const char *channel, const char *audiodev, int use_sdr, int samplerate, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, double squelch_db, int ptt, int station_length, double fast_seize, enum mode mode, const char *operator, double length_1, double length_2, double length_3);
|
||||||
|
void imts_destroy(sender_t *sender);
|
||||||
|
void imts_loss_indication(imts_t *imts, double loss_time);
|
||||||
|
void imts_signal_indication(imts_t *imts);
|
||||||
|
void imts_receive_tone(imts_t *imts, int tone, double elapsed, double amplitude);
|
||||||
|
void imts_lost_tone(imts_t *imts, int tone, double elapsed);
|
||||||
|
void imts_tone_sent(imts_t *imts, int tone);
|
||||||
|
|
|
@ -0,0 +1,289 @@
|
||||||
|
/* MTS/IMTS main
|
||||||
|
*
|
||||||
|
* (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 <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include "../libsample/sample.h"
|
||||||
|
#include "../libmobile/main_mobile.h"
|
||||||
|
#include "../libdebug/debug.h"
|
||||||
|
#include "../libtimer/timer.h"
|
||||||
|
#include "../libmobile/call.h"
|
||||||
|
#include "../liboptions/options.h"
|
||||||
|
#include "../amps/tones.h"
|
||||||
|
#include "../amps/outoforder.h"
|
||||||
|
#include "../amps/noanswer.h"
|
||||||
|
#include "../amps/invalidnumber.h"
|
||||||
|
#include "../amps/congestion.h"
|
||||||
|
#include "imts.h"
|
||||||
|
#include "dsp.h"
|
||||||
|
|
||||||
|
/* settings */
|
||||||
|
static double squelch_db = -INFINITY;
|
||||||
|
static int ptt = 0;
|
||||||
|
static int station_length = 0; /* defined by mode */
|
||||||
|
static double fast_seize = 0.0;
|
||||||
|
static enum mode mode = MODE_IMTS;
|
||||||
|
static char operator[32] = "010";
|
||||||
|
static double detector_test_length_1 = 0.0;
|
||||||
|
static double detector_test_length_2 = 0.0;
|
||||||
|
static double detector_test_length_3 = 0.0;
|
||||||
|
|
||||||
|
void print_help(const char *arg0)
|
||||||
|
{
|
||||||
|
main_mobile_print_help(arg0, "-b 5 ");
|
||||||
|
/* - - */
|
||||||
|
printf(" -S --squelch <dB> | auto\n");
|
||||||
|
printf(" Use given RF level to detect loss of signal. When the signal gets lost\n");
|
||||||
|
printf(" and stays below this level, the connection is released.\n");
|
||||||
|
printf(" Use 'auto' to do automatic noise floor calibration to detect loss.\n");
|
||||||
|
printf(" Only works with SDR! (disabled by default)\n");
|
||||||
|
printf(" -P --push-to-talk\n");
|
||||||
|
printf(" Allow push-to-talk operation for IMTS mode. (MTS always uses it.)\n");
|
||||||
|
printf(" This adds extra delay to received audio, to eliminate noise when the\n");
|
||||||
|
printf(" transmitter of the phone is turned off. Also this disables release on\n");
|
||||||
|
printf(" loss of RF signal. (Squelch is required for this to operate.)\n");
|
||||||
|
printf(" -5 --five\n");
|
||||||
|
printf(" -7 --seven\n");
|
||||||
|
printf(" Force station ID length (default is 7 for IMTS, 5 for MTS)\n");
|
||||||
|
printf(" -F --fast-seize <delay in ms>\n");
|
||||||
|
printf(" To compensate audio processing latency, give delay when to respond,\n");
|
||||||
|
printf(" after detection of Guard tone from mobile phone.\n");
|
||||||
|
printf(" Run software in loopback mode '-l 2' to measure round trip delay.\n");
|
||||||
|
printf(" Substract delay from 350 ms. If the phone has different Guard tone\n");
|
||||||
|
printf(" length, substract from that value.\n");
|
||||||
|
printf(" -D --detector-test <idle length> <seize lenght> <silence length>\n");
|
||||||
|
printf(" Transmit detector test signal, to adjust decoder inside mobile phone.\n");
|
||||||
|
printf(" Give length of idle / seize and silence in seconds. Listen to it with\n");
|
||||||
|
printf(" a radio receiver. To exclude an element, set its length to '0'.\n");
|
||||||
|
printf(" Example: '-D 0.5 0.5 0' plays alternating idle/seize tone.\n");
|
||||||
|
printf("\nMTS mode options\n");
|
||||||
|
printf(" -M --mts\n");
|
||||||
|
printf(" Run base station in MTS mode, rather than in IMTS mode.\n");
|
||||||
|
printf(" -O --operator <number>\n");
|
||||||
|
printf(" Give number to dial when mobile station initiated a call in MTS mode.\n");
|
||||||
|
printf(" Because there is no dial on the mobile phone, operator assistance is\n");
|
||||||
|
printf(" required to complete the call.\n");
|
||||||
|
printf(" By default, the operator '%s' is dialed.\n", operator);
|
||||||
|
printf(" -D --detector-test <600 Hz length> <1500 Hz lenght> <silence length>\n");
|
||||||
|
printf(" Transmit detector test signal, to adjust decoder inside MTS phone.\n");
|
||||||
|
printf(" Give length of 600/1500 Hz and silence in seconds. Listen to it with\n");
|
||||||
|
printf(" a radio receiver. To exclude an element, set its length to '0'.\n");
|
||||||
|
printf(" Example: '-D 0.5 0.5 0' plays alternating 600/1500 Hz tone.\n");
|
||||||
|
printf("\nstation-id: Give %d digits of station-id, you don't need to enter it after\n", station_length);
|
||||||
|
printf(" every start of this program.\n");
|
||||||
|
main_mobile_print_hotkeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_options(void)
|
||||||
|
{
|
||||||
|
main_mobile_add_options();
|
||||||
|
option_add('S', "squelch", 1);
|
||||||
|
option_add('P', "push-to-talk", 0);
|
||||||
|
option_add('5', "five", 0);
|
||||||
|
option_add('7', "seven", 0);
|
||||||
|
option_add('F', "fast-seize", 1);
|
||||||
|
option_add('D', "decoder-test", 3);
|
||||||
|
option_add('M', "mts", 0);
|
||||||
|
option_add('O', "operator", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_options(int short_option, int argi, char **argv)
|
||||||
|
{
|
||||||
|
switch (short_option) {
|
||||||
|
case 'S':
|
||||||
|
if (!strcasecmp(argv[argi], "auto"))
|
||||||
|
squelch_db = 0.0;
|
||||||
|
else
|
||||||
|
squelch_db = atof(argv[argi]);
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
ptt = 1;
|
||||||
|
break;
|
||||||
|
case '5':
|
||||||
|
station_length = 5;
|
||||||
|
break;
|
||||||
|
case '7':
|
||||||
|
station_length = 7;
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
fast_seize = atof(argv[argi]) / 1000.0;
|
||||||
|
if (fast_seize < 0.0)
|
||||||
|
fast_seize = 0.0;
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
detector_test_length_1 = atof(argv[argi++]);
|
||||||
|
detector_test_length_2 = atof(argv[argi++]);
|
||||||
|
detector_test_length_3 = atof(argv[argi++]);
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
mode = MODE_MTS;
|
||||||
|
ptt = 1;
|
||||||
|
break;
|
||||||
|
case 'O':
|
||||||
|
strncpy(operator, argv[argi], sizeof(operator) - 1);
|
||||||
|
operator[sizeof(operator) - 1] = '\0';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return main_mobile_handle_options(short_option, argi, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int rc, argi;
|
||||||
|
const char *station_id = "";
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* init common tones */
|
||||||
|
init_tones();
|
||||||
|
init_outoforder();
|
||||||
|
init_noanswer();
|
||||||
|
init_invalidnumber();
|
||||||
|
init_congestion();
|
||||||
|
|
||||||
|
main_mobile_init();
|
||||||
|
|
||||||
|
/* handle options / config file */
|
||||||
|
add_options();
|
||||||
|
rc = options_config_file("~/.osmocom/analog/imts.conf", handle_options);
|
||||||
|
if (rc < 0)
|
||||||
|
return 0;
|
||||||
|
argi = options_command_line(argc, argv, handle_options);
|
||||||
|
if (argi <= 0)
|
||||||
|
return argi;
|
||||||
|
|
||||||
|
if (!station_length) {
|
||||||
|
if (mode == MODE_IMTS)
|
||||||
|
station_length = 7;
|
||||||
|
else
|
||||||
|
station_length = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argi < argc) {
|
||||||
|
station_id = argv[argi];
|
||||||
|
if ((int)strlen(station_id) != station_length) {
|
||||||
|
printf("Given station ID '%s' does not have %d digits\n", station_id, station_length);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!num_kanal) {
|
||||||
|
printf("No channel (\"Kanal\") is specified, Use '-k list' to get a list of all channels.\n\n");
|
||||||
|
print_help(argv[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!strcasecmp(kanal[0], "list")) {
|
||||||
|
imts_list_channels();
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (use_sdr) {
|
||||||
|
/* set audiodev */
|
||||||
|
for (i = 0; i < num_kanal; i++)
|
||||||
|
audiodev[i] = "sdr";
|
||||||
|
num_audiodev = num_kanal;
|
||||||
|
}
|
||||||
|
if (num_kanal == 1 && num_audiodev == 0)
|
||||||
|
num_audiodev = 1; /* use default */
|
||||||
|
if (num_kanal != num_audiodev) {
|
||||||
|
fprintf(stderr, "You need to specify as many sound devices as you have channels.\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SDR always requires emphasis */
|
||||||
|
if (use_sdr) {
|
||||||
|
do_pre_emphasis = 1;
|
||||||
|
do_de_emphasis = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!do_pre_emphasis || !do_de_emphasis) {
|
||||||
|
fprintf(stderr, "*******************************************************************************\n");
|
||||||
|
fprintf(stderr, "I suggest to let me do pre- and de-emphasis (options -p -d)!\n");
|
||||||
|
fprintf(stderr, "Use a transmitter/receiver without emphasis and let me do that!\n");
|
||||||
|
fprintf(stderr, "Because FSK signaling does not use emphasis, I like to control emphasis by\n");
|
||||||
|
fprintf(stderr, "myself for best results.\n");
|
||||||
|
fprintf(stderr, "*******************************************************************************\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == MODE_IMTS && !fast_seize && latency > 5 && loopback == 0) {
|
||||||
|
fprintf(stderr, "*******************************************************************************\n");
|
||||||
|
fprintf(stderr, "It is required to have a low latency in order to respond to phone's seizure\n");
|
||||||
|
fprintf(stderr, "fast enough! Please reduce buffer size to 5 ms via option: '-b 5'\n");
|
||||||
|
fprintf(stderr, "If this causes buffer underruns, use the 'Fast Seize' mode, see help.\n");
|
||||||
|
fprintf(stderr, "*******************************************************************************\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == MODE_MTS && !use_sdr && loopback == 0) {
|
||||||
|
fprintf(stderr, "*******************************************************************************\n");
|
||||||
|
fprintf(stderr, "MTS mode requires use of SDR, because base station is controlled by Squelch.\n");
|
||||||
|
fprintf(stderr, "*******************************************************************************\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
if (mode == MODE_MTS && isinf(squelch_db) < 0 && loopback == 0) {
|
||||||
|
fprintf(stderr, "*******************************************************************************\n");
|
||||||
|
fprintf(stderr, "MTS mode requires use of Squelch. Please set Squelch level, see help.\n");
|
||||||
|
fprintf(stderr, "*******************************************************************************\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptt && isinf(squelch_db) < 0 && loopback == 0) {
|
||||||
|
fprintf(stderr, "*******************************************************************************\n");
|
||||||
|
fprintf(stderr, "Cannot use push-to-talk feature without Squelch option.\n");
|
||||||
|
fprintf(stderr, "*******************************************************************************\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* no squelch in loopback mode */
|
||||||
|
if (loopback)
|
||||||
|
squelch_db = -INFINITY;
|
||||||
|
|
||||||
|
/* inits */
|
||||||
|
fm_init(fast_math);
|
||||||
|
dsp_init();
|
||||||
|
imts_init();
|
||||||
|
|
||||||
|
/* create transceiver instance */
|
||||||
|
for (i = 0; i < num_kanal; i++) {
|
||||||
|
rc = imts_create(kanal[i], audiodev[i], use_sdr, samplerate, rx_gain, do_pre_emphasis, do_de_emphasis, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, squelch_db, ptt, station_length, fast_seize, mode, operator, detector_test_length_1, detector_test_length_2, detector_test_length_3);
|
||||||
|
if (rc < 0) {
|
||||||
|
fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
printf("Base station on channel %s ready, please tune transmitter to %.3f MHz and receiver to %.3f MHz. (%.3f MHz offset)\n", kanal[i], imts_channel2freq(kanal[i], 0) / 1e6, imts_channel2freq(kanal[i], 1) / 1e6, imts_channel2freq(kanal[i], 2) / 1e6);
|
||||||
|
}
|
||||||
|
|
||||||
|
main_mobile(&quit, latency, interval, NULL, station_id, station_length);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
/* destroy transceiver instance */
|
||||||
|
while (sender_head)
|
||||||
|
imts_destroy(sender_head);
|
||||||
|
|
||||||
|
/* exits */
|
||||||
|
fm_exit();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ struct debug_cat {
|
||||||
{ "nmt", "\033[1;34m" },
|
{ "nmt", "\033[1;34m" },
|
||||||
{ "amps", "\033[1;34m" },
|
{ "amps", "\033[1;34m" },
|
||||||
{ "r2000", "\033[1;34m" },
|
{ "r2000", "\033[1;34m" },
|
||||||
|
{ "imts", "\033[1;34m" },
|
||||||
{ "jollycom", "\033[1;34m" },
|
{ "jollycom", "\033[1;34m" },
|
||||||
{ "frame", "\033[0;36m" },
|
{ "frame", "\033[0;36m" },
|
||||||
{ "call", "\033[0;37m" },
|
{ "call", "\033[0;37m" },
|
||||||
|
|
|
@ -14,19 +14,20 @@
|
||||||
#define DNMT 7
|
#define DNMT 7
|
||||||
#define DAMPS 8
|
#define DAMPS 8
|
||||||
#define DR2000 9
|
#define DR2000 9
|
||||||
#define DJOLLY 10
|
#define DIMTS 10
|
||||||
#define DFRAME 11
|
#define DJOLLY 11
|
||||||
#define DCALL 12
|
#define DFRAME 12
|
||||||
#define DMNCC 13
|
#define DCALL 13
|
||||||
#define DDB 14
|
#define DMNCC 14
|
||||||
#define DTRANS 15
|
#define DDB 15
|
||||||
#define DDMS 16
|
#define DTRANS 16
|
||||||
#define DSMS 17
|
#define DDMS 17
|
||||||
#define DSDR 18
|
#define DSMS 18
|
||||||
#define DUHD 19
|
#define DSDR 19
|
||||||
#define DSOAPY 20
|
#define DUHD 20
|
||||||
#define DWAVE 21
|
#define DSOAPY 21
|
||||||
#define DRADIO 22
|
#define DWAVE 22
|
||||||
|
#define DRADIO 23
|
||||||
|
|
||||||
void get_win_size(int *w, int *h);
|
void get_win_size(int *w, int *h);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue